Skip to content

Commit

Permalink
0.17.1 released
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Bender committed May 27, 2018
1 parent 292ccf9 commit e2f5fb3
Show file tree
Hide file tree
Showing 81 changed files with 3,579 additions and 1,544 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.17.0'
__version__ = '0.17.1'
__author__ = 'Joel Bender'
__email__ = '[email protected]'

Expand Down
50 changes: 32 additions & 18 deletions py25/bacpypes/apdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,38 +56,51 @@ def register_error_type(klass):
# encode_max_segments_accepted/decode_max_segments_accepted
#

_max_segments_accepted_encoding = [
None, 2, 4, 8, 16, 32, 64, None,
]

def encode_max_segments_accepted(arg):
"""Encode the maximum number of segments the device will accept, Section
20.1.2.4"""
w = 0
while (arg and not arg & 1):
w += 1
arg = (arg >> 1)
return w
20.1.2.4, and if the device says it can only accept one segment it shouldn't
say that it supports segmentation!"""
# unspecified
if not arg:
return 0

if arg > 64:
return 7

# the largest number not greater than the arg
for i in range(6, 0, -1):
if _max_segments_accepted_encoding[i] <= arg:
return i

raise ValueError("invalid max max segments accepted: {0}".format(arg))

def decode_max_segments_accepted(arg):
"""Decode the maximum number of segments the device will accept, Section
20.1.2.4"""
return arg and (1 << arg) or None
return _max_segments_accepted_encoding[arg]

#
# encode_max_apdu_length_accepted/decode_max_apdu_length_accepted
#

_max_apdu_response_encoding = [50, 128, 206, 480, 1024, 1476, None, None,
_max_apdu_length_encoding = [50, 128, 206, 480, 1024, 1476, None, None,
None, None, None, None, None, None, None, None]

def encode_max_apdu_length_accepted(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]):
if (arg >= _max_apdu_length_encoding[i]):
return i

raise ValueError("invalid max APDU length accepted: {0}".format(arg))

def decode_max_apdu_length_accepted(arg):
v = _max_apdu_response_encoding[arg]
v = _max_apdu_length_encoding[arg]
if not v:
raise ValueError("invalid max APDU length accepted: {0}".format(arg))

Expand Down Expand Up @@ -173,7 +186,7 @@ def encode(self, pdu):
if self.apduSA:
buff += 0x02
pdu.put(buff)
pdu.put((encode_max_segments_accepted(self.apduMaxSegs) << 4) + encode_max_apdu_length_accepted(self.apduMaxResp))
pdu.put((self.apduMaxSegs << 4) + self.apduMaxResp)
pdu.put(self.apduInvokeID)
if self.apduSeg:
pdu.put(self.apduSeq)
Expand Down Expand Up @@ -254,8 +267,8 @@ def decode(self, pdu):
self.apduMor = ((buff & 0x04) != 0)
self.apduSA = ((buff & 0x02) != 0)
buff = pdu.get()
self.apduMaxSegs = decode_max_segments_accepted( (buff >> 4) & 0x07 )
self.apduMaxResp = decode_max_apdu_length_accepted( buff & 0x0F )
self.apduMaxSegs = (buff >> 4) & 0x07
self.apduMaxResp = buff & 0x0F
self.apduInvokeID = pdu.get()
if self.apduSeg:
self.apduSeq = pdu.get()
Expand Down Expand Up @@ -693,6 +706,7 @@ def decode(self, apdu):
# create a tag list and decode the rest of the data
self._tag_list = TagList()
self._tag_list.decode(apdu)
if _debug: APCISequence._debug(" - tag list: %r", self._tag_list)

# pass the taglist to the Sequence for additional decoding
Sequence.decode(self, self._tag_list)
Expand Down Expand Up @@ -1307,7 +1321,7 @@ class LifeSafetyOperationRequest(ConfirmedRequestSequence):
[ Element('requestingProcessIdentifier', Unsigned, 0)
, Element('requestingSource', CharacterString, 1)
, Element('request', LifeSafetyOperation, 2)
, Element('objectIdentifier', ObjectIdentifier, 3)
, Element('objectIdentifier', ObjectIdentifier, 3, True)
]

register_confirmed_request_type(LifeSafetyOperationRequest)
Expand Down Expand Up @@ -1491,7 +1505,7 @@ class RemoveListElementRequest(ConfirmedRequestSequence):
sequenceElements = \
[ Element('objectIdentifier', ObjectIdentifier, 0)
, Element('propertyIdentifier', PropertyIdentifier, 1)
, Element('propertyArrayIndex', Unsigned, 2)
, Element('propertyArrayIndex', Unsigned, 2, True)
, Element('listOfElements', Any, 3)
]

Expand All @@ -1501,9 +1515,9 @@ class RemoveListElementRequest(ConfirmedRequestSequence):

class DeviceCommunicationControlRequestEnableDisable(Enumerated):
enumerations = \
{ 'enable':0
, 'disable':1
, 'disableInitiation':2
{ 'enable': 0
, 'disable': 1
, 'disableInitiation': 2
}

class DeviceCommunicationControlRequest(ConfirmedRequestSequence):
Expand Down
169 changes: 89 additions & 80 deletions py25/bacpypes/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@

# for computing protocol services supported
from .apdu import confirmed_request_types, unconfirmed_request_types, \
ConfirmedServiceChoice, UnconfirmedServiceChoice
ConfirmedServiceChoice, UnconfirmedServiceChoice, \
IAmRequest
from .basetypes import ServicesSupported

# basic services
Expand All @@ -53,16 +54,16 @@ class DeviceInfo(DebugContents):
'maxSegmentsAccepted',
)

def __init__(self):
def __init__(self, device_identifier, address):
# this information is from an IAmRequest
self.deviceIdentifier = None # device identifier
self.address = None # LocalStation or RemoteStation
self.deviceIdentifier = device_identifier
self.address = address

self.maxApduLengthAccepted = 1024 # maximum APDU device will accept
self.segmentationSupported = 'noSegmentation' # normally no segmentation
self.maxSegmentsAccepted = None # None iff no segmentation
self.vendorID = None # vendor identifier

self.maxNpduLength = 1497 # maximum we can send in transit
self.maxSegmentsAccepted = None # value for proposed/actual window size
self.maxNpduLength = None # maximum we can send in transit (see 19.4)

bacpypes_debugging(DeviceInfo)

Expand All @@ -72,127 +73,134 @@ def __init__(self):

class DeviceInfoCache:

def __init__(self):
def __init__(self, device_info_class=DeviceInfo):
if _debug: DeviceInfoCache._debug("__init__")

# a little error checking
if not issubclass(device_info_class, DeviceInfo):
raise ValueError("not a DeviceInfo subclass: %r" % (device_info_class,))

# empty cache
self.cache = {}

# class for new records
self.device_info_class = device_info_class

def has_device_info(self, key):
"""Return true iff cache has information about the device."""
if _debug: DeviceInfoCache._debug("has_device_info %r", key)

return key in self.cache

def add_device_info(self, apdu):
def iam_device_info(self, apdu):
"""Create a device information record based on the contents of an
IAmRequest and put it in the cache."""
if _debug: DeviceInfoCache._debug("add_device_info %r", apdu)
if _debug: DeviceInfoCache._debug("iam_device_info %r", apdu)

# get the existing cache record by identifier
info = self.get_device_info(apdu.iAmDeviceIdentifier[1])
if _debug: DeviceInfoCache._debug(" - info: %r", info)
# make sure the apdu is an I-Am
if not isinstance(apdu, IAmRequest):
raise ValueError("not an IAmRequest: %r" % (apdu,))

# update existing record
if info:
if (info.address == apdu.pduSource):
return
# get the device instance
device_instance = apdu.iAmDeviceIdentifier[1]

info.address = apdu.pduSource
else:
# get the existing record by address (creates a new record)
info = self.get_device_info(apdu.pduSource)
if _debug: DeviceInfoCache._debug(" - info: %r", info)
# get the existing cache record if it exists
device_info = self.cache.get(device_instance, None)

info.deviceIdentifier = apdu.iAmDeviceIdentifier[1]
# maybe there is a record for this address
if not device_info:
device_info = self.cache.get(apdu.pduSource, None)

# update the rest of the values
info.maxApduLengthAccepted = apdu.maxAPDULengthAccepted
info.segmentationSupported = apdu.segmentationSupported
info.vendorID = apdu.vendorID
# make a new one using the class provided
if not device_info:
device_info = self.device_info_class(device_instance, apdu.pduSource)

# say this is an updated record
self.update_device_info(info)
# jam in the correct values
device_info.deviceIdentifier = device_instance
device_info.address = apdu.pduSource
device_info.maxApduLengthAccepted = apdu.maxAPDULengthAccepted
device_info.segmentationSupported = apdu.segmentationSupported
device_info.vendorID = apdu.vendorID

# tell the cache this is an updated record
self.update_device_info(device_info)

def get_device_info(self, key):
"""Return the known information about the device. If the key is the
address of an unknown device, build a generic device information record
add put it in the cache."""
if _debug: DeviceInfoCache._debug("get_device_info %r", key)

if isinstance(key, int):
current_info = self.cache.get(key, None)
# get the info if it's there
device_info = self.cache.get(key, None)
if _debug: DeviceInfoCache._debug(" - device_info: %r", device_info)

elif not isinstance(key, Address):
raise TypeError("key must be integer or an address")
return device_info

elif key.addrType not in (Address.localStationAddr, Address.remoteStationAddr):
raise TypeError("address must be a local or remote station")

else:
current_info = self.cache.get(key, None)
if not current_info:
current_info = DeviceInfo()
current_info.address = key
current_info._cache_keys = (None, key)
current_info._ref_count = 1

self.cache[key] = current_info
else:
if _debug: DeviceInfoCache._debug(" - reference bump")
current_info._ref_count += 1

if _debug: DeviceInfoCache._debug(" - current_info: %r", current_info)

return current_info

def update_device_info(self, info):
def update_device_info(self, device_info):
"""The application has updated one or more fields in the device
information record and the cache needs to be updated to reflect the
changes. If this is a cached version of a persistent record then this
is the opportunity to update the database."""
if _debug: DeviceInfoCache._debug("update_device_info %r", info)
if _debug: DeviceInfoCache._debug("update_device_info %r", device_info)

cache_id, cache_address = info._cache_keys
# give this a reference count if it doesn't have one
if not hasattr(device_info, '_ref_count'):
device_info._ref_count = 0

if (cache_id is not None) and (info.deviceIdentifier != cache_id):
# get the current keys
cache_id, cache_address = getattr(device_info, '_cache_keys', (None, None))

if (cache_id is not None) and (device_info.deviceIdentifier != cache_id):
if _debug: DeviceInfoCache._debug(" - device identifier updated")

# remove the old reference, add the new one
del self.cache[cache_id]
self.cache[info.deviceIdentifier] = info

cache_id = info.deviceIdentifier
self.cache[device_info.deviceIdentifier] = device_info

if (cache_address is not None) and (info.address != cache_address):
if (cache_address is not None) and (device_info.address != cache_address):
if _debug: DeviceInfoCache._debug(" - device address updated")

# remove the old reference, add the new one
del self.cache[cache_address]
self.cache[info.address] = info

cache_address = info.address
self.cache[device_info.address] = device_info

# update the keys
info._cache_keys = (cache_id, cache_address)
device_info._cache_keys = (device_info.deviceIdentifier, device_info.address)

def acquire(self, key):
"""Return the known information about the device and mark the record
as being used by a segmenation state machine."""
if _debug: DeviceInfoCache._debug("acquire %r", key)

if isinstance(key, int):
device_info = self.cache.get(key, None)

elif not isinstance(key, Address):
raise TypeError("key must be integer or an address")

elif key.addrType not in (Address.localStationAddr, Address.remoteStationAddr):
raise TypeError("address must be a local or remote station")

def release_device_info(self, info):
else:
device_info = self.cache.get(key, None)

if device_info:
if _debug: DeviceInfoCache._debug(" - reference bump")
device_info._ref_count += 1

if _debug: DeviceInfoCache._debug(" - device_info: %r", device_info)

return device_info

def release(self, device_info):
"""This function is called by the segmentation state machine when it
has finished with the device information."""
if _debug: DeviceInfoCache._debug("release_device_info %r", info)
if _debug: DeviceInfoCache._debug("release %r", device_info)

# this information record might be used by more than one SSM
if info._ref_count > 1:
if _debug: DeviceInfoCache._debug(" - multiple references")
info._ref_count -= 1
return
if device_info._ref_count == 0:
raise RuntimeError("reference count")

cache_id, cache_address = info._cache_keys
if cache_id is not None:
del self.cache[cache_id]
if cache_address is not None:
del self.cache[cache_address]
if _debug: DeviceInfoCache._debug(" - released")
# decrement the reference count
device_info._ref_count -= 1

bacpypes_debugging(DeviceInfoCache)

Expand Down Expand Up @@ -619,3 +627,4 @@ def __init__(self, localAddress, eID=None):
self.nsap.bind(self.bip)

bacpypes_debugging(BIPNetworkApplication)

Loading

0 comments on commit e2f5fb3

Please sign in to comment.