diff --git a/doc/source/index.rst b/doc/source/index.rst index d941512..2653441 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -66,7 +66,7 @@ essential components of a BACpypes application and how the pieces fit together. tutorial/tutorial001.rst tutorial/tutorial002.rst tutorial/tutorial003.rst - tutorial/tutorial004.rst + tutorial/addressing.rst tutorial/tutorial006.rst tutorial/iocb.rst tutorial/capability.rst diff --git a/doc/source/tutorial/addressing.rst b/doc/source/tutorial/addressing.rst new file mode 100644 index 0000000..bb127fb --- /dev/null +++ b/doc/source/tutorial/addressing.rst @@ -0,0 +1,210 @@ +.. BACpypes tutorial lesson 4 + +Addressing +========== + +BACnet addresses come in five delicious flavors: + +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. + +BACpypes address objects are used as the source and destination for PDUs and +are also keys to dictionaries for looking up device in information and +organizing requests and responses with devices. + +Building an Address +------------------- + +The Address class other related classes are in the pdu module. + +Local Stations +~~~~~~~~~~~~~~ + +The Address class is the base class from which the other classes are derived, +but for this tutorial, we'll start with the simplest:: + + >>> from bacpypes.pdu import LocalStation + +Local station addresses are one or more octets of binary data. For the +simplest networks they are a single octet, for Ethernet and BACnet/IP they +are six octets long. There is no restriction on the length of an address in +BACpypes. + +A local station address is contructed by passing the octet string as bytes or +a byte array, and their string representation is hex notation:: + + >>> addr1 = Address(b'123456') + >>> print(addr1) + 0x313233343536 + +For local stations on simple networks the constructor will accept unsigned +integers with the simple string output:: + + >>> addr2 = Address(12) + >>> print(addr2) + 12 + +The underlying components of the address are always byte strings:: + + >>> addr1.addrAddr + b'123456' + >>> addr1.addrAddr + b'\x01' + +When the byte string is six octets long and the next to last octet is 0xBA and +the last octet is in the range 0xC0 to 0xCF, the string output and repr value +will be presented as an IPv4 address:: + + >>> LocalStation(b'\1\2\3\4\xba\xc0') + + +and it will include the port number if it is not the standard port:: + + >>> LocalStation(b'\1\2\3\4\xba\xc3') + + +Local Broadcast +~~~~~~~~~~~~~~~ + +The local broadcast address is used in the destination of a PDU that is to be +sent to all of the devices on a network, and if the network layer can detect +if it received a PDU as the result of another station broadcasting it. There +are no parameters for constructing one:: + + >>> from bacpypes.pdu import LocalBroadcast + >>> print(LocalBroadcast()) + * + +The string output represents any address. + +Remote Station +~~~~~~~~~~~~~~ + +A remote station address is used in BACnet networking when the source and/or +destination is on a network other than the one considered local. The first +parameter is the network number, which must be a valid BACnet network number, +and the second parameter is a byte string or unsigned integer like the local +station:: + + >>> from bacpypes.pdu import RemoteStation + >>> print(RemoteStation(15, 75)) + 15:75 + >>> print(RemoteStation(15, b'123456')) + 15:0x313233343536 + +The string output is the network number and address separated by a colon. + +Remote Broadcast +~~~~~~~~~~~~~~~~ + +A remote broadcast station is used as a destination address when sending a PDU +to all of the devices on a remote network. The only constructor parameter is +the network number, which must be a valid BACnet network number: + + >>> from bacpypes.pdu import RemoteBroadcast + >>> print(RemoteBroadcast(17)) + 17:* + +The string output is the network number number, a colon, and an asterisk for +any address. + +GlobalBroadcast +~~~~~~~~~~~~~~~ + +The global broadcast address is used to send PDUs to all devices. It has no +constructor parameters:: + + >>> from bacpypes.pdu import GlobalBroadcast + >>> print(GlobalBroadcast()) + *:* + +The string output is an asterisk for any network, a colon, and an asterisk for +and address. + +Address Parsing +--------------- + +The basic Address class can parse the string form of all of the address types +and a few more for older applications and notation that has appeared in +other tutorials. + +.. note:: + The Address class cannot "morph" into an instance of one of its subclasses + so to determine what kind of address it is check the addrType attribute. + +For example:: + + >>> from bacpypes.pdu import Address + >>> Address(1).addrType == Address.localStationAddr + True + +And addresses created this way are identical:: + + >>> Address(1) == LocalStation(b'\01') + True + +Unlike the LocalStation, the Address can take the string form of an integer:: + + >>> Address("2") == LocalStation(b'\02') + True + +And can interpret hex strings of various types:: + + >>> Address("0x0304") == LocalStation(b'\3\4') + True + >>> Address("X'050607'") == LocalStation(b'\5\6\7') + True + +It interprets the asterisk as a local broadcast:: + + >>> Address("*") == LocalBroadcast() + True + +And remote stations and remote broadcasts mathing the other output:: + + >>> Address("1:2") == RemoteStation(1, 2) + True + >>> Address("3:*") == RemoteBroadcast(3) + True + +And the global broadcast:: + + >>> Address("*:*") == GlobalBroadcast() + True + +IPv4 Addresses +~~~~~~~~~~~~~~ + +Because they appear so often, the address parsing has special patterns for +recognizing IPv4 addresses in CIDR notation along with an optional port +number:: + + >>> Address("192.168.1.2").addrAddr + b'\xc0\xa8\x01\x02\xba\xc0' + + >>> Address("192.168.1.2:47809").addrAddr + b'\xc0\xa8\x01\x02\xba\xc1' + +For addresses that also include a subnet mask to calculate broadcast addresses, +the CIDR notation is available:: + + >>> hex(Address("192.168.3.4/24").addrSubnet) + '0xc0a80300' + +And for calculating the address tuple for use with socket functions:: + + >>> Address("192.168.5.6/16").addrBroadcastTuple + ('192.168.255.255', 47808) + diff --git a/doc/source/tutorial/tutorial004.rst b/doc/source/tutorial/tutorial004.rst deleted file mode 100644 index 593b6fc..0000000 --- a/doc/source/tutorial/tutorial004.rst +++ /dev/null @@ -1,22 +0,0 @@ -.. BACpypes tutorial lesson 4 - -Addressing -========== - -BACnet addresses come in five delicious flavors: - -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/py25/bacpypes/__init__.py b/py25/bacpypes/__init__.py index 85c5628..d73a0ae 100755 --- a/py25/bacpypes/__init__.py +++ b/py25/bacpypes/__init__.py @@ -18,7 +18,7 @@ if _sys.platform not in _supported_platforms: # Project Metadata # -__version__ = '0.15.1' +__version__ = '0.15.2' __author__ = 'Joel Bender' __email__ = 'joel@carrickbender.com' diff --git a/py25/bacpypes/core.py b/py25/bacpypes/core.py index 098a357..9f1a033 100755 --- a/py25/bacpypes/core.py +++ b/py25/bacpypes/core.py @@ -23,16 +23,82 @@ taskManager = None deferredFns = [] sleeptime = 0.0 +# +# stop +# + +def stop(*args): + """Call to stop running, may be called with a signum and frame + parameter if called as a signal handler.""" + if _debug: stop._debug("stop") + global running, taskManager + + if args: + sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + sys.stderr.flush() + + running = False + + # trigger the task manager event + if taskManager and taskManager.trigger: + if _debug: stop._debug(" - trigger") + taskManager.trigger.set() + +bacpypes_debugging(stop) + +# +# print_stack +# + +def print_stack(sig, frame): + """Signal handler to print a stack trace and some interesting values.""" + if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) + global running, deferredFns, sleeptime + + sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + + sys.stderr.write("---------- globals\n") + sys.stderr.write(" running: %r\n" % (running,)) + sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) + sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) + + sys.stderr.write("---------- stack\n") + traceback.print_stack(frame) + + # make a list of interesting frames + flist = [] + f = frame + while f.f_back: + flist.append(f) + f = f.f_back + + # reverse the list so it is in the same order as print_stack + flist.reverse() + for f in flist: + sys.stderr.write("---------- frame: %s\n" % (f,)) + for k, v in f.f_locals.items(): + sys.stderr.write(" %s: %r\n" % (k, v)) + + sys.stderr.flush() + +bacpypes_debugging(print_stack) + # # run # SPIN = 1.0 -def run(spin=SPIN): - if _debug: run._debug("run spin=%r", spin) +def run(spin=SPIN, sigterm=stop, sigusr1=print_stack): + if _debug: run._debug("run spin=%r sigterm=%r, sigusr1=%r", spin, sigterm, sigusr1) global running, taskManager, deferredFns, sleeptime + # install the signal handlers if they have been provided (issue #112) + if (sigterm is not None) and hasattr(signal, 'SIGTERM'): + signal.signal(signal.SIGTERM, sigterm) + if (sigusr1 is not None) and hasattr(signal, 'SIGUSR1'): + signal.signal(signal.SIGUSR1, sigusr1) + # reference the task manager (a singleton) taskManager = TaskManager() @@ -141,75 +207,6 @@ def run_once(): if _debug: run_once._exception("an error has occurred: %s", err) bacpypes_debugging(run_once) - -# -# stop -# - -def stop(*args): - """Call to stop running, may be called with a signum and frame - parameter if called as a signal handler.""" - if _debug: stop._debug("stop") - global running, taskManager - - if args: - sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - sys.stderr.flush() - - running = False - - # trigger the task manager event - if taskManager and taskManager.trigger: - if _debug: stop._debug(" - trigger") - taskManager.trigger.set() - -bacpypes_debugging(stop) - -# set a TERM signal handler -if hasattr(signal, 'SIGTERM'): - signal.signal(signal.SIGTERM, stop) - -# -# print_stack -# - -def print_stack(sig, frame): - """Signal handler to print a stack trace and some interesting values.""" - if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) - global running, deferredFns, sleeptime - - sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - - sys.stderr.write("---------- globals\n") - sys.stderr.write(" running: %r\n" % (running,)) - sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) - sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) - - sys.stderr.write("---------- stack\n") - traceback.print_stack(frame) - - # make a list of interesting frames - flist = [] - f = frame - while f.f_back: - flist.append(f) - f = f.f_back - - # reverse the list so it is in the same order as print_stack - flist.reverse() - for f in flist: - sys.stderr.write("---------- frame: %s\n" % (f,)) - for k, v in f.f_locals.items(): - sys.stderr.write(" %s: %r\n" % (k, v)) - - sys.stderr.flush() - -bacpypes_debugging(print_stack) - -# set a USR1 signal handler to print a stack trace -if hasattr(signal, 'SIGUSR1'): - signal.signal(signal.SIGUSR1, print_stack) - # # deferred # @@ -242,3 +239,4 @@ def enable_sleeping(stime=0.001): sleeptime = stime bacpypes_debugging(enable_sleeping) + diff --git a/py27/bacpypes/__init__.py b/py27/bacpypes/__init__.py index 85c5628..d73a0ae 100755 --- a/py27/bacpypes/__init__.py +++ b/py27/bacpypes/__init__.py @@ -18,7 +18,7 @@ if _sys.platform not in _supported_platforms: # Project Metadata # -__version__ = '0.15.1' +__version__ = '0.15.2' __author__ = 'Joel Bender' __email__ = 'joel@carrickbender.com' diff --git a/py27/bacpypes/core.py b/py27/bacpypes/core.py index 00b4a16..6de8034 100755 --- a/py27/bacpypes/core.py +++ b/py27/bacpypes/core.py @@ -23,6 +23,64 @@ taskManager = None deferredFns = [] sleeptime = 0.0 +# +# stop +# + +@bacpypes_debugging +def stop(*args): + """Call to stop running, may be called with a signum and frame + parameter if called as a signal handler.""" + if _debug: stop._debug("stop") + global running, taskManager + + if args: + sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + sys.stderr.flush() + + running = False + + # trigger the task manager event + if taskManager and taskManager.trigger: + if _debug: stop._debug(" - trigger") + taskManager.trigger.set() + +# +# print_stack +# + +@bacpypes_debugging +def print_stack(sig, frame): + """Signal handler to print a stack trace and some interesting values.""" + if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) + global running, deferredFns, sleeptime + + sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + + sys.stderr.write("---------- globals\n") + sys.stderr.write(" running: %r\n" % (running,)) + sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) + sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) + + sys.stderr.write("---------- stack\n") + traceback.print_stack(frame) + + # make a list of interesting frames + flist = [] + f = frame + while f.f_back: + flist.append(f) + f = f.f_back + + # reverse the list so it is in the same order as print_stack + flist.reverse() + for f in flist: + sys.stderr.write("---------- frame: %s\n" % (f,)) + for k, v in f.f_locals.items(): + sys.stderr.write(" %s: %r\n" % (k, v)) + + sys.stderr.flush() + # # run # @@ -30,10 +88,16 @@ sleeptime = 0.0 SPIN = 1.0 @bacpypes_debugging -def run(spin=SPIN): - if _debug: run._debug("run spin=%r", spin) +def run(spin=SPIN, sigterm=stop, sigusr1=print_stack): + if _debug: run._debug("run spin=%r sigterm=%r, sigusr1=%r", spin, sigterm, sigusr1) global running, taskManager, deferredFns, sleeptime + # install the signal handlers if they have been provided (issue #112) + if (sigterm is not None) and hasattr(signal, 'SIGTERM'): + signal.signal(signal.SIGTERM, sigterm) + if (sigusr1 is not None) and hasattr(signal, 'SIGUSR1'): + signal.signal(signal.SIGUSR1, sigusr1) + # reference the task manager (a singleton) taskManager = TaskManager() @@ -140,72 +204,6 @@ def run_once(): except Exception as err: if _debug: run_once._exception("an error has occurred: %s", err) -# -# stop -# - -@bacpypes_debugging -def stop(*args): - """Call to stop running, may be called with a signum and frame - parameter if called as a signal handler.""" - if _debug: stop._debug("stop") - global running, taskManager - - if args: - sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - sys.stderr.flush() - - running = False - - # trigger the task manager event - if taskManager and taskManager.trigger: - if _debug: stop._debug(" - trigger") - taskManager.trigger.set() - -# set a TERM signal handler -if hasattr(signal, 'SIGTERM'): - signal.signal(signal.SIGTERM, stop) - -# -# print_stack -# - -@bacpypes_debugging -def print_stack(sig, frame): - """Signal handler to print a stack trace and some interesting values.""" - if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) - global running, deferredFns, sleeptime - - sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - - sys.stderr.write("---------- globals\n") - sys.stderr.write(" running: %r\n" % (running,)) - sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) - sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) - - sys.stderr.write("---------- stack\n") - traceback.print_stack(frame) - - # make a list of interesting frames - flist = [] - f = frame - while f.f_back: - flist.append(f) - f = f.f_back - - # reverse the list so it is in the same order as print_stack - flist.reverse() - for f in flist: - sys.stderr.write("---------- frame: %s\n" % (f,)) - for k, v in f.f_locals.items(): - sys.stderr.write(" %s: %r\n" % (k, v)) - - sys.stderr.flush() - -# set a USR1 signal handler to print a stack trace -if hasattr(signal, 'SIGUSR1'): - signal.signal(signal.SIGUSR1, print_stack) - # # deferred # @@ -237,3 +235,4 @@ def enable_sleeping(stime=0.001): # set the sleep time sleeptime = stime + diff --git a/py34/bacpypes/__init__.py b/py34/bacpypes/__init__.py index 98b78da..012cff6 100755 --- a/py34/bacpypes/__init__.py +++ b/py34/bacpypes/__init__.py @@ -18,7 +18,7 @@ if _sys.platform not in _supported_platforms: # Project Metadata # -__version__ = '0.15.1' +__version__ = '0.15.2' __author__ = 'Joel Bender' __email__ = 'joel@carrickbender.com' diff --git a/py34/bacpypes/core.py b/py34/bacpypes/core.py index 00b4a16..6de8034 100755 --- a/py34/bacpypes/core.py +++ b/py34/bacpypes/core.py @@ -23,6 +23,64 @@ taskManager = None deferredFns = [] sleeptime = 0.0 +# +# stop +# + +@bacpypes_debugging +def stop(*args): + """Call to stop running, may be called with a signum and frame + parameter if called as a signal handler.""" + if _debug: stop._debug("stop") + global running, taskManager + + if args: + sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + sys.stderr.flush() + + running = False + + # trigger the task manager event + if taskManager and taskManager.trigger: + if _debug: stop._debug(" - trigger") + taskManager.trigger.set() + +# +# print_stack +# + +@bacpypes_debugging +def print_stack(sig, frame): + """Signal handler to print a stack trace and some interesting values.""" + if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) + global running, deferredFns, sleeptime + + sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) + + sys.stderr.write("---------- globals\n") + sys.stderr.write(" running: %r\n" % (running,)) + sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) + sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) + + sys.stderr.write("---------- stack\n") + traceback.print_stack(frame) + + # make a list of interesting frames + flist = [] + f = frame + while f.f_back: + flist.append(f) + f = f.f_back + + # reverse the list so it is in the same order as print_stack + flist.reverse() + for f in flist: + sys.stderr.write("---------- frame: %s\n" % (f,)) + for k, v in f.f_locals.items(): + sys.stderr.write(" %s: %r\n" % (k, v)) + + sys.stderr.flush() + # # run # @@ -30,10 +88,16 @@ sleeptime = 0.0 SPIN = 1.0 @bacpypes_debugging -def run(spin=SPIN): - if _debug: run._debug("run spin=%r", spin) +def run(spin=SPIN, sigterm=stop, sigusr1=print_stack): + if _debug: run._debug("run spin=%r sigterm=%r, sigusr1=%r", spin, sigterm, sigusr1) global running, taskManager, deferredFns, sleeptime + # install the signal handlers if they have been provided (issue #112) + if (sigterm is not None) and hasattr(signal, 'SIGTERM'): + signal.signal(signal.SIGTERM, sigterm) + if (sigusr1 is not None) and hasattr(signal, 'SIGUSR1'): + signal.signal(signal.SIGUSR1, sigusr1) + # reference the task manager (a singleton) taskManager = TaskManager() @@ -140,72 +204,6 @@ def run_once(): except Exception as err: if _debug: run_once._exception("an error has occurred: %s", err) -# -# stop -# - -@bacpypes_debugging -def stop(*args): - """Call to stop running, may be called with a signum and frame - parameter if called as a signal handler.""" - if _debug: stop._debug("stop") - global running, taskManager - - if args: - sys.stderr.write("===== TERM Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - sys.stderr.flush() - - running = False - - # trigger the task manager event - if taskManager and taskManager.trigger: - if _debug: stop._debug(" - trigger") - taskManager.trigger.set() - -# set a TERM signal handler -if hasattr(signal, 'SIGTERM'): - signal.signal(signal.SIGTERM, stop) - -# -# print_stack -# - -@bacpypes_debugging -def print_stack(sig, frame): - """Signal handler to print a stack trace and some interesting values.""" - if _debug: print_stack._debug("print_stack, %r, %r", sig, frame) - global running, deferredFns, sleeptime - - sys.stderr.write("==== USR1 Signal, %s\n" % time.strftime("%d-%b-%Y %H:%M:%S")) - - sys.stderr.write("---------- globals\n") - sys.stderr.write(" running: %r\n" % (running,)) - sys.stderr.write(" deferredFns: %r\n" % (deferredFns,)) - sys.stderr.write(" sleeptime: %r\n" % (sleeptime,)) - - sys.stderr.write("---------- stack\n") - traceback.print_stack(frame) - - # make a list of interesting frames - flist = [] - f = frame - while f.f_back: - flist.append(f) - f = f.f_back - - # reverse the list so it is in the same order as print_stack - flist.reverse() - for f in flist: - sys.stderr.write("---------- frame: %s\n" % (f,)) - for k, v in f.f_locals.items(): - sys.stderr.write(" %s: %r\n" % (k, v)) - - sys.stderr.flush() - -# set a USR1 signal handler to print a stack trace -if hasattr(signal, 'SIGUSR1'): - signal.signal(signal.SIGUSR1, print_stack) - # # deferred # @@ -237,3 +235,4 @@ def enable_sleeping(stime=0.001): # set the sleep time sleeptime = stime + diff --git a/release_to_pypi.sh b/release_to_pypi.sh index c94f9be..95c9246 100755 --- a/release_to_pypi.sh +++ b/release_to_pypi.sh @@ -10,12 +10,20 @@ rm -Rfv build/ # python2.5 setup.py bdist_egg # rm -Rfv build/ -for ver in 2.7 3.4; do - python$ver setup.py bdist_egg - python$ver setup.py bdist_wheel - rm -Rfv build/ +for version in 2.7 3.4 3.5 3.6; do + if [ -a "`which python$version`" ]; then + python$version setup.py bdist_egg + python$version setup.py bdist_wheel + rm -Rfv build/ + fi done +echo +echo This is what was built... +echo +ls -1 dist/ +echo + read -p "Upload to PyPI? [y/n/x] " yesno || exit 1 if [ "$yesno" = "y" ] ;