Skip to content

Commit

Permalink
ready for the next release
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelBender committed Aug 26, 2016
2 parents 5f72c48 + e2437d4 commit d3a593e
Show file tree
Hide file tree
Showing 44 changed files with 492 additions and 364 deletions.
2 changes: 1 addition & 1 deletion py25/bacpypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Project Metadata
#

__version__ = '0.14.0'
__version__ = '0.14.1'
__author__ = 'Joel Bender'
__email__ = '[email protected]'

Expand Down
6 changes: 4 additions & 2 deletions py25/bacpypes/apdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,10 @@ def decode_max_segments_accepted(arg):
None, None, None, None, None, None, None, None]

def encode_max_apdu_length_accepted(arg):
for i, v in enumerate(_max_apdu_response_encoding):
if (v <= arg):
"""Return the encoding of the highest encodable value less than the
value of the arg."""
for i in range(5, -1, -1):
if (arg >= _max_apdu_response_encoding[i]):
return i

raise ValueError("invalid max APDU length accepted: {0}".format(arg))
Expand Down
11 changes: 9 additions & 2 deletions py25/bacpypes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def add_device_info(self, apdu):
info.deviceIdentifier = apdu.iAmDeviceIdentifier[1]

# update the rest of the values
info.maxApduLengthAccepted = apdu.maxApduLengthAccepted
info.maxApduLengthAccepted = apdu.maxAPDULengthAccepted
info.segmentationSupported = apdu.segmentationSupported
info.vendorID = apdu.vendorID

Expand Down Expand Up @@ -248,7 +248,7 @@ class LocalDeviceObject(DeviceObject):
{ 'maxApduLengthAccepted': 1024
, 'segmentationSupported': 'segmentedBoth'
, 'maxSegmentsAccepted': 16
, 'apduSegmentTimeout': 20000
, 'apduSegmentTimeout': 5000
, 'apduTimeout': 3000
, 'numberOfApduRetries': 3
}
Expand All @@ -273,6 +273,13 @@ def __init__(self, **kwargs):
if 'localTime' in kwargs:
raise RuntimeError("localTime is provided by LocalDeviceObject and cannot be overridden")

# check for a minimum value
if kwargs['maxApduLengthAccepted'] < 50:
raise ValueError("invalid max APDU length accepted")

# dump the updated attributes
if _debug: LocalDeviceObject._debug(" - updated kwargs: %r", kwargs)

# proceed as usual
DeviceObject.__init__(self, **kwargs)

Expand Down
67 changes: 39 additions & 28 deletions py25/bacpypes/appservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,19 @@ class SSM(OneShotTask, DebugContents):
, 'initialSequenceNumber', 'actualWindowSize', 'proposedWindowSize'
)

def __init__(self, sap, localDevice, remoteDevice):
def __init__(self, sap, remoteDevice):
"""Common parts for client and server segmentation."""
if _debug: SSM._debug("__init__ %r %r %r", sap, localDevice, remoteDevice)
if _debug: SSM._debug("__init__ %r %r", sap, remoteDevice)
OneShotTask.__init__(self)

self.ssmSAP = sap # service access point
self.localDevice = localDevice # local device information, DeviceObject
self.remoteDevice = remoteDevice # remote device information, a DeviceInfo instance
self.invokeID = None # invoke ID

self.state = IDLE # initial state
self.segmentAPDU = None # refers to request or response
self.segmentSize = None # how big the pieces are
self.segmentCount = None
self.maxSegmentsAccepted = None # maximum number of segments

self.retryCount = None
self.segmentRetryCount = None
Expand All @@ -74,13 +72,9 @@ def __init__(self, sap, localDevice, remoteDevice):
self.actualWindowSize = None
self.proposedWindowSize = None

# look for our segmentation parameters from our device object
if localDevice:
self.maxApduLengthAccepted = localDevice.maxApduLengthAccepted
self.segmentationSupported = localDevice.segmentationSupported
else:
self.maxApduLengthAccepted = 1024
self.segmentationSupported = 'noSegmentation'
# the maximum number of segments starts out being what's in the SAP
# which is the defaults or values from the local device.
self.maxSegmentsAccepted = self.ssmSAP.maxSegmentsAccepted

def start_timer(self, msecs):
if _debug: SSM._debug("start_timer %r", msecs)
Expand Down Expand Up @@ -159,11 +153,11 @@ def get_segment(self, indx):
segAPDU = ConfirmedRequestPDU(self.segmentAPDU.apduService)

segAPDU.apduMaxSegs = self.maxSegmentsAccepted
segAPDU.apduMaxResp = self.maxApduLengthAccepted
segAPDU.apduMaxResp = self.ssmSAP.maxApduLengthAccepted
segAPDU.apduInvokeID = self.invokeID;

# segmented response accepted?
segAPDU.apduSA = self.segmentationSupported in ('segmentedReceive', 'segmentedBoth')
segAPDU.apduSA = self.ssmSAP.segmentationSupported in ('segmentedReceive', 'segmentedBoth')
if _debug: SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA)

elif self.segmentAPDU.apduType == ComplexAckPDU.pduType:
Expand Down Expand Up @@ -240,9 +234,9 @@ def FillWindow(self, seqNum):

class ClientSSM(SSM):

def __init__(self, sap, localDevice, remoteDevice):
if _debug: ClientSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
SSM.__init__(self, sap, localDevice, remoteDevice)
def __init__(self, sap, remoteDevice):
if _debug: ClientSSM._debug("__init__ %s %r", sap, remoteDevice)
SSM.__init__(self, sap, remoteDevice)

# initialize the retry count
self.retryCount = 0
Expand Down Expand Up @@ -290,7 +284,7 @@ def indication(self, apdu):
# the maximum conveyable by the internetwork to the remote device, and
# the maximum APDU size accepted by the remote device.
self.segmentSize = min(
self.maxApduLengthAccepted,
self.ssmSAP.maxApduLengthAccepted,
self.remoteDevice.maxNpduLength,
self.remoteDevice.maxApduLengthAccepted,
)
Expand Down Expand Up @@ -318,7 +312,7 @@ def indication(self, apdu):

# make sure we support segmented transmit if we need to
if self.segmentCount > 1:
if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
if self.ssmSAP.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
if _debug: ClientSSM._debug(" - local device can't send segmented requests")
abort = self.abort(AbortReason.segmentationNotSupported)
self.response(abort)
Expand Down Expand Up @@ -522,7 +516,7 @@ def await_confirmation(self, apdu):
self.set_state(COMPLETED)
self.response(apdu)

elif self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
elif self.ssmSAP.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
if _debug: ClientSSM._debug(" - local device can't receive segmented messages")
abort = self.abort(AbortReason.segmentationNotSupported)
self.response(abort)
Expand Down Expand Up @@ -650,9 +644,9 @@ def segmented_confirmation_timeout(self):

class ServerSSM(SSM):

def __init__(self, sap, localDevice, remoteDevice):
if _debug: ServerSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
SSM.__init__(self, sap, localDevice, remoteDevice)
def __init__(self, sap, remoteDevice):
if _debug: ServerSSM._debug("__init__ %s %r", sap, remoteDevice)
SSM.__init__(self, sap, remoteDevice)

def set_state(self, newState, timer=0):
"""This function is called when the client wants to change state."""
Expand Down Expand Up @@ -769,7 +763,7 @@ def confirmation(self, apdu):
if _debug: ServerSSM._debug(" - segmentation required, %d segments", self.segmentCount)

# make sure we support segmented transmit
if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
if self.ssmSAP.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
if _debug: ServerSSM._debug(" - server can't send segmented responses")
abort = self.abort(AbortReason.segmentationNotSupported)
self.reponse(abort)
Expand Down Expand Up @@ -850,10 +844,16 @@ def idle(self, apdu):
if _debug: ServerSSM._debug(" - client actually supports segmented receive")
self.remoteDevice.segmentationSupported = 'segmentedReceive'

if _debug: ServerSSM._debug(" - tell the cache the info has been updated")
self.ssmSAP.deviceInfoCache.update_device_info(self.remoteDevice)

elif self.remoteDevice.segmentationSupported == 'segmentedTransmit':
if _debug: ServerSSM._debug(" - client actually supports both segmented transmit and receive")
self.remoteDevice.segmentationSupported = 'segmentedBoth'

if _debug: ServerSSM._debug(" - tell the cache the info has been updated")
self.ssmSAP.deviceInfoCache.update_device_info(self.remoteDevice)

elif self.remoteDevice.segmentationSupported == 'segmentedReceive':
pass

Expand All @@ -878,7 +878,7 @@ def idle(self, apdu):
return

# make sure we support segmented requests
if self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
if self.ssmSAP.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
abort = self.abort(AbortReason.segmentationNotSupported)
self.response(abort)
return
Expand Down Expand Up @@ -1074,14 +1074,25 @@ def __init__(self, localDevice=None, deviceInfoCache=None, sap=None, cid=None):
# server settings
self.serverTransactions = []

# confirmed request settings
# confirmed request defaults
self.retryCount = 3
self.retryTimeout = 3000
self.maxApduLengthAccepted = 1024

# segmentation settings
# segmentation defaults
self.segmentationSupported = 'noSegmentation'
self.segmentTimeout = 1500
self.maxSegmentsAccepted = 8

# local device object provides these
if localDevice:
self.retryCount = localDevice.numberOfApduRetries
self.retryTimeout = localDevice.apduTimeout
self.segmentationSupported = localDevice.segmentationSupported
self.segmentTimeout = localDevice.apduSegmentTimeout
self.maxSegmentsAccepted = localDevice.maxSegmentsAccepted
self.maxApduLengthAccepted = localDevice.maxApduLengthAccepted

# how long the state machine is willing to wait for the application
# layer to form a response and send it
self.applicationTimeout = 3000
Expand Down Expand Up @@ -1132,7 +1143,7 @@ def confirmation(self, pdu):
remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduSource)

# build a server transaction
tr = ServerSSM(self, self.localDevice, remoteDevice)
tr = ServerSSM(self, remoteDevice)

# add it to our transactions to track it
self.serverTransactions.append(tr)
Expand Down Expand Up @@ -1232,7 +1243,7 @@ def sap_indication(self, apdu):
if _debug: StateMachineAccessPoint._debug(" - remoteDevice: %r", remoteDevice)

# create a client transaction state machine
tr = ClientSSM(self, self.localDevice, remoteDevice)
tr = ClientSSM(self, remoteDevice)
if _debug: StateMachineAccessPoint._debug(" - client segmentation state machine: %r", tr)

# add it to our transactions to track it
Expand Down
85 changes: 63 additions & 22 deletions py25/bacpypes/consolelogging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
Console Logging
"""

import os
import sys
import logging
import logging.handlers

try:
from argparse import ArgumentParser as _ArgumentParser
Expand All @@ -22,12 +24,17 @@ class _ArgumentParser: pass
_debug = 0
_log = ModuleLogger(globals())

# configuration
BACPYPES_DEBUG = os.getenv('BACPYPES_DEBUG', '')
BACPYPES_MAXBYTES = int(os.getenv('BACPYPES_MAXBYTES', 1048576))
BACPYPES_BACKUPCOUNT = int(os.getenv('BACPYPES_BACKUPCOUNT', 5))

#
# ConsoleLogHandler
#

def ConsoleLogHandler(loggerRef='', level=logging.DEBUG, color=None):
"""Add a stream handler to stderr with our custom formatter to a logger."""
def ConsoleLogHandler(loggerRef='', handler=None, level=logging.DEBUG, color=None):
"""Add a handler to stderr with our custom formatter to a logger."""
if isinstance(loggerRef, logging.Logger):
pass

Expand All @@ -52,15 +59,16 @@ def ConsoleLogHandler(loggerRef='', level=logging.DEBUG, color=None):
elif hasattr(loggerRef.parent, 'globs'):
loggerRef.parent.globs['_debug'] += 1

# make a debug handler
hdlr = logging.StreamHandler()
hdlr.setLevel(level)
# make a handler if one wasn't provided
if not handler:
handler = logging.StreamHandler()
handler.setLevel(level)

# use our formatter
hdlr.setFormatter(LoggingFormatter(color))
handler.setFormatter(LoggingFormatter(color))

# add it to the logger
loggerRef.addHandler(hdlr)
loggerRef.addHandler(handler)

# make sure the logger has at least this level
loggerRef.setLevel(level)
Expand All @@ -76,7 +84,7 @@ class ArgumentParser(_ArgumentParser):
by adding the common command line arguments found in BACpypes applications.
--buggers list the debugging logger names
--debug [DBEUG [DEBUG ...]] attach a console to loggers
--debug [DEBUG [DEBUG ...]] attach a handler to loggers
--color debug in color
"""

Expand All @@ -97,7 +105,7 @@ def __init__(self, **kwargs):

# add a way to attach debuggers
self.add_argument('--debug', nargs='*',
help="add console log handler to each debugging logger",
help="add a log handler to each debugging logger",
)

# add a way to turn on color debugging
Expand All @@ -123,21 +131,54 @@ def parse_args(self, *args, **kwargs):
# check for debug
if result_args.debug is None:
# --debug not specified
bug_list = []
result_args.debug = []
elif not result_args.debug:
# --debug, but no arguments
bug_list = ["__main__"]
else:
# --debug with arguments
bug_list = result_args.debug

# attach any that are specified
if result_args.color:
for i, debug_name in enumerate(bug_list):
ConsoleLogHandler(debug_name, color=(i % 6) + 2)
else:
for debug_name in bug_list:
ConsoleLogHandler(debug_name)
result_args.debug = ["__main__"]

# check for debugging from the environment
if BACPYPES_DEBUG:
result_args.debug.extend(BACPYPES_DEBUG.split())

# keep track of which files are going to be used
file_handlers = {}

# loop through the bug list
for i, debug_name in enumerate(result_args.debug):
color = (i % 6) + 2 if result_args.color else None

debug_specs = debug_name.split(':')
if len(debug_specs) == 1:
ConsoleLogHandler(debug_name, color=color)
else:
# the debugger name is just the first component
debug_name = debug_specs[0]

# if the file is already being used, use the already created handler
file_name = debug_specs[1]
if file_name in file_handlers:
handler = file_handlers[file_name]
else:
if len(debug_specs) >= 3:
maxBytes = int(debug_specs[2])
else:
maxBytes = BACPYPES_MAXBYTES
if len(debug_specs) >= 4:
backupCount = int(debug_specs[3])
else:
backupCount = BACPYPES_BACKUPCOUNT

# create a handler
handler = logging.handlers.RotatingFileHandler(
file_name, maxBytes=maxBytes, backupCount=backupCount,
)
handler.setLevel(logging.DEBUG)

# save it for more than one instance
file_handlers[file_name] = handler

# use this handler, no color
ConsoleLogHandler(debug_name, handler=handler)

# return what was parsed
return result_args
Expand Down
2 changes: 1 addition & 1 deletion py27/bacpypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Project Metadata
#

__version__ = '0.14.0'
__version__ = '0.14.1'
__author__ = 'Joel Bender'
__email__ = '[email protected]'

Expand Down
Loading

0 comments on commit d3a593e

Please sign in to comment.