mirror of
https://github.com/JoelBender/bacpypes
synced 2025-09-28 22:15:23 +08:00
sync py34 with py27
This commit is contained in:
parent
54a5104d35
commit
f78a8c9d67
|
@ -68,11 +68,19 @@ def decode_max_apdu_segments(arg):
|
|||
# encode_max_apdu_response/decode_max_apdu_response
|
||||
#
|
||||
|
||||
_max_apdu_response_encoding = {0:50, 1:128, 2:206, 3:480, 4:1024, 5:1476}
|
||||
|
||||
def encode_max_apdu_response(arg):
|
||||
return {50:0, 128:1, 206:2, 480:3, 1024:4, 1476:5}.get(arg)
|
||||
encodings = _max_apdu_response_encoding.items()
|
||||
encodings.sort(lambda x, y: y[1] - x[1])
|
||||
for i, v in encodings:
|
||||
if (v <= arg):
|
||||
return i
|
||||
|
||||
raise ValueError("invalid max APDU response encoding: {0}".format(arg))
|
||||
|
||||
def decode_max_apdu_response(arg):
|
||||
return {0:50, 1:128, 2:206, 3:480, 4:1024, 5:1476}.get(arg)
|
||||
return _max_apdu_response_encoding.get(arg)
|
||||
|
||||
#
|
||||
# APCI
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
Application Module
|
||||
"""
|
||||
|
||||
from .debugging import bacpypes_debugging, ModuleLogger
|
||||
from .debugging import bacpypes_debugging, DebugContents, ModuleLogger
|
||||
from .comm import ApplicationServiceElement, bind
|
||||
|
||||
from .pdu import Address
|
||||
from .pdu import Address, LocalStation, RemoteStation
|
||||
|
||||
from .primitivedata import Atomic, Date, Null, ObjectIdentifier, Time, Unsigned
|
||||
from .constructeddata import Any, Array, ArrayOf
|
||||
|
@ -38,6 +38,152 @@ from .apdu import \
|
|||
_debug = 0
|
||||
_log = ModuleLogger(globals())
|
||||
|
||||
#
|
||||
# DeviceInfo
|
||||
#
|
||||
|
||||
@bacpypes_debugging
|
||||
class DeviceInfo(DebugContents):
|
||||
|
||||
_debug_contents = (
|
||||
'deviceIdentifier',
|
||||
'address',
|
||||
'maxApduLengthAccepted',
|
||||
'segmentationSupported',
|
||||
'vendorID',
|
||||
'maxNpduLength',
|
||||
'maxSegmentsAccepted',
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
# this information is from an IAmRequest
|
||||
self.deviceIdentifier = None # device identifier
|
||||
self.address = None # LocalStation or RemoteStation
|
||||
self.maxApduLengthAccepted = 1024 # maximum APDU device will accept
|
||||
self.segmentationSupported = 'noSegmentation' # normally 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
|
||||
|
||||
#
|
||||
# DeviceInfoCache
|
||||
#
|
||||
|
||||
@bacpypes_debugging
|
||||
class DeviceInfoCache:
|
||||
|
||||
def __init__(self):
|
||||
if _debug: DeviceInfoCache._debug("__init__")
|
||||
|
||||
# empty cache
|
||||
self.cache = {}
|
||||
|
||||
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):
|
||||
"""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)
|
||||
|
||||
# get the existing cache record by identifier
|
||||
info = self.get_device_info(apdu.iAmDeviceIdentifier[1])
|
||||
if _debug: DeviceInfoCache._debug(" - info: %r", info)
|
||||
|
||||
# update existing record
|
||||
if info:
|
||||
if (info.address == apdu.pduSource):
|
||||
return
|
||||
|
||||
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)
|
||||
|
||||
info.deviceIdentifier = apdu.iAmDeviceIdentifier[1]
|
||||
|
||||
# update the rest of the values
|
||||
info.maxApduLengthAccepted = apdu.maxApduLengthAccepted
|
||||
info.segmentationSupported = apdu.segmentationSupported
|
||||
info.vendorID = apdu.vendorID
|
||||
|
||||
# say this is an updated record
|
||||
self.update_device_info(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)
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
self.cache[key] = current_info
|
||||
|
||||
if _debug: DeviceInfoCache._debug(" - current_info: %r", current_info)
|
||||
|
||||
return current_info
|
||||
|
||||
def update_device_info(self, 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)
|
||||
|
||||
cache_id, cache_address = info._cache_keys
|
||||
|
||||
if (cache_id is not None) and (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
|
||||
|
||||
if (cache_address is not None) and (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
|
||||
|
||||
# update the keys
|
||||
info._cache_keys = (cache_id, cache_address)
|
||||
|
||||
def release_device_info(self, 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)
|
||||
|
||||
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]
|
||||
|
||||
#
|
||||
# CurrentDateProperty
|
||||
#
|
||||
|
@ -146,13 +292,19 @@ class LocalDeviceObject(DeviceObject):
|
|||
@bacpypes_debugging
|
||||
class Application(ApplicationServiceElement):
|
||||
|
||||
def __init__(self, localDevice, localAddress, aseID=None):
|
||||
if _debug: Application._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID)
|
||||
def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None):
|
||||
if _debug: Application._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID)
|
||||
ApplicationServiceElement.__init__(self, aseID)
|
||||
|
||||
# keep track of the local device
|
||||
self.localDevice = localDevice
|
||||
|
||||
# use the provided cache or make a default one
|
||||
if deviceInfoCache:
|
||||
self.deviceInfoCache = deviceInfoCache
|
||||
else:
|
||||
self.deviceInfoCache = DeviceInfoCache()
|
||||
|
||||
# bind the device object to this application
|
||||
localDevice._app = self
|
||||
|
||||
|
@ -610,9 +762,9 @@ class Application(ApplicationServiceElement):
|
|||
@bacpypes_debugging
|
||||
class BIPSimpleApplication(Application):
|
||||
|
||||
def __init__(self, localDevice, localAddress, aseID=None):
|
||||
if _debug: BIPSimpleApplication._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID)
|
||||
Application.__init__(self, localDevice, localAddress, aseID)
|
||||
def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None):
|
||||
if _debug: BIPSimpleApplication._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID)
|
||||
Application.__init__(self, localDevice, localAddress, deviceInfoCache, aseID)
|
||||
|
||||
# include a application decoder
|
||||
self.asap = ApplicationServiceAccessPoint()
|
||||
|
@ -621,6 +773,10 @@ class BIPSimpleApplication(Application):
|
|||
# can know if it should support segmentation
|
||||
self.smap = StateMachineAccessPoint(localDevice)
|
||||
|
||||
# the segmentation state machines need access to the same device
|
||||
# information cache as the application
|
||||
self.smap.deviceInfoCache = self.deviceInfoCache
|
||||
|
||||
# a network service access point will be needed
|
||||
self.nsap = NetworkServiceAccessPoint()
|
||||
|
||||
|
|
|
@ -22,38 +22,6 @@ from .apdu import AbortPDU, AbortReason, ComplexAckPDU, \
|
|||
_debug = 0
|
||||
_log = ModuleLogger(globals())
|
||||
|
||||
#
|
||||
# DeviceInfo
|
||||
#
|
||||
|
||||
class DeviceInfo(DebugContents):
|
||||
|
||||
_debug_contents = (
|
||||
'address',
|
||||
'maxNpduLength',
|
||||
'segmentationSupported',
|
||||
'maxApduLengthAccepted',
|
||||
'maxSegmentsAccepted',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, address=None,
|
||||
maxNpduLength=1024,
|
||||
segmentationSupported='noSegmentation',
|
||||
maxApduLengthAccepted=1024,
|
||||
maxSegmentsAccepted=None,
|
||||
):
|
||||
if not isinstance(address, (None.__class__, Address, LocalStation, RemoteStation)):
|
||||
raise TypeError("address")
|
||||
|
||||
self.address = address # LocalStation or RemoteStation
|
||||
self.maxNpduLength = maxNpduLength # maximum we can send in transit
|
||||
self.segmentationSupported = segmentationSupported # normally no segmentation
|
||||
self.maxApduLengthAccepted = maxApduLengthAccepted # how big to divide up apdu's
|
||||
self.maxSegmentsAccepted = maxSegmentsAccepted # limit on how many segments to recieve
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# SSM - Segmentation State Machine
|
||||
#
|
||||
|
@ -76,26 +44,27 @@ class SSM(OneShotTask, DebugContents):
|
|||
, 'SEGMENTED_RESPONSE', 'SEGMENTED_CONFIRMATION', 'COMPLETED', 'ABORTED'
|
||||
]
|
||||
|
||||
_debug_contents = ('ssmSAP', 'remoteDevice', 'invokeID'
|
||||
_debug_contents = ('ssmSAP', 'localDevice', 'remoteDevice', 'invokeID'
|
||||
, 'state', 'segmentAPDU', 'segmentSize', 'segmentCount', 'maxSegmentsAccepted'
|
||||
, 'retryCount', 'segmentRetryCount', 'sentAllSegments', 'lastSequenceNumber'
|
||||
, 'initialSequenceNumber', 'actualWindowSize', 'proposedWindowSize'
|
||||
)
|
||||
|
||||
def __init__(self, sap):
|
||||
def __init__(self, sap, localDevice, remoteDevice):
|
||||
"""Common parts for client and server segmentation."""
|
||||
if _debug: SSM._debug("__init__ %r", sap)
|
||||
if _debug: SSM._debug("__init__ %r %r %r", sap, localDevice, remoteDevice)
|
||||
OneShotTask.__init__(self)
|
||||
|
||||
self.ssmSAP = sap # reference to the service access point
|
||||
self.remoteDevice = None # remote device
|
||||
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 client will accept
|
||||
self.maxSegmentsAccepted = None # maximum number of segments
|
||||
|
||||
self.retryCount = None
|
||||
self.segmentRetryCount = None
|
||||
|
@ -105,6 +74,14 @@ class SSM(OneShotTask, DebugContents):
|
|||
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'
|
||||
|
||||
def start_timer(self, msecs):
|
||||
if _debug: SSM._debug("start_timer %r", msecs)
|
||||
|
||||
|
@ -179,15 +156,12 @@ class SSM(OneShotTask, DebugContents):
|
|||
segAPDU = ConfirmedRequestPDU(self.segmentAPDU.apduService)
|
||||
|
||||
segAPDU.apduMaxSegs = self.maxSegmentsAccepted
|
||||
segAPDU.apduMaxResp = self.ssmSAP.maxApduLengthAccepted
|
||||
segAPDU.apduMaxResp = self.maxApduLengthAccepted
|
||||
segAPDU.apduInvokeID = self.invokeID;
|
||||
|
||||
# segmented response accepted?
|
||||
segAPDU.apduSA = ((self.ssmSAP.segmentationSupported == 'segmentedBoth') \
|
||||
or (self.ssmSAP.segmentationSupported == 'segmentedReceive'))
|
||||
if _debug:
|
||||
SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA)
|
||||
SSM._debug(" - self.ssmSAP.segmentationSupported: %r", self.ssmSAP.segmentationSupported)
|
||||
segAPDU.apduSA = self.segmentationSupported in ('segmentedReceive', 'segmentedBoth')
|
||||
if _debug: SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA)
|
||||
|
||||
elif self.segmentAPDU.apduType == ComplexAckPDU.pduType:
|
||||
if _debug: SSM._debug(" - complex ack context")
|
||||
|
@ -262,9 +236,9 @@ class SSM(OneShotTask, DebugContents):
|
|||
@bacpypes_debugging
|
||||
class ClientSSM(SSM):
|
||||
|
||||
def __init__(self, sap):
|
||||
if _debug: ClientSSM._debug("__init__ %s", sap)
|
||||
SSM.__init__(self, sap)
|
||||
def __init__(self, sap, localDevice, remoteDevice):
|
||||
if _debug: ClientSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
|
||||
SSM.__init__(self, sap, localDevice, remoteDevice)
|
||||
|
||||
# initialize the retry count
|
||||
self.retryCount = 0
|
||||
|
@ -278,8 +252,15 @@ class ClientSSM(SSM):
|
|||
|
||||
# completed or aborted, remove tracking
|
||||
if (newState == COMPLETED) or (newState == ABORTED):
|
||||
if _debug: ClientSSM._debug(" - remove from active transactions")
|
||||
self.ssmSAP.clientTransactions.remove(self)
|
||||
|
||||
if _debug: ClientSSM._debug(" - release device information")
|
||||
self.ssmSAP.deviceInfoCache.release_device_info(self.remoteDevice)
|
||||
|
||||
# help along the garbage collector
|
||||
self.ssmSAP = self.localDevice = self.remoteDevice = None
|
||||
|
||||
def request(self, apdu):
|
||||
"""This function is called by client transaction functions when it wants
|
||||
to send a message to the device."""
|
||||
|
@ -304,24 +285,20 @@ class ClientSSM(SSM):
|
|||
# save the request and set the segmentation context
|
||||
self.set_segmentation_context(apdu)
|
||||
|
||||
# get information about the device
|
||||
self.remoteDevice = self.ssmSAP.get_device_info(apdu.pduDestination)
|
||||
if _debug: ClientSSM._debug(" - remoteDevice: %r", self.remoteDevice)
|
||||
|
||||
# the segment size is the minimum of the maximum size I can transmit
|
||||
# (assumed to have no local buffer limitations), the maximum conveyable
|
||||
# by the internetwork to the remote device, and the maximum APDU size
|
||||
# accepted by the remote device.
|
||||
self.segmentSize = min(self.remoteDevice.maxNpduLength, self.remoteDevice.maxApduLengthAccepted)
|
||||
# the segment size is the minimum of the maximum size I can transmit,
|
||||
# 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.remoteDevice.maxNpduLength,
|
||||
self.remoteDevice.maxApduLengthAccepted,
|
||||
)
|
||||
if _debug: ClientSSM._debug(" - segment size: %r", self.segmentSize)
|
||||
|
||||
# save the maximum number of segments acceptable in the reply
|
||||
# the maximum number of segments acceptable in the reply
|
||||
if apdu.apduMaxSegs is not None:
|
||||
# this request overrides the default
|
||||
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
||||
else:
|
||||
# use the default in the device definition
|
||||
self.maxSegmentsAccepted = self.ssmSAP.maxSegmentsAccepted
|
||||
|
||||
# save the invoke ID
|
||||
self.invokeID = apdu.apduInvokeID
|
||||
|
@ -340,12 +317,12 @@ class ClientSSM(SSM):
|
|||
|
||||
# make sure we support segmented transmit if we need to
|
||||
if self.segmentCount > 1:
|
||||
if (self.ssmSAP.segmentationSupported != 'segmentedTransmit') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'):
|
||||
if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
|
||||
if _debug: ClientSSM._debug(" - local device can't send segmented requests")
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.response(abort)
|
||||
return
|
||||
if (self.remoteDevice.segmentationSupported != 'segmentedReceive') and (self.remoteDevice.segmentationSupported != 'segmentedBoth'):
|
||||
if self.remoteDevice.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
|
||||
if _debug: ClientSSM._debug(" - remote device can't receive segmented requests")
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.response(abort)
|
||||
|
@ -544,7 +521,7 @@ class ClientSSM(SSM):
|
|||
self.set_state(COMPLETED)
|
||||
self.response(apdu)
|
||||
|
||||
elif (self.ssmSAP.segmentationSupported != 'segmentedReceive') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'):
|
||||
elif self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
|
||||
if _debug: ClientSSM._debug(" - local device can't receive segmented messages")
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.response(abort)
|
||||
|
@ -671,9 +648,9 @@ class ClientSSM(SSM):
|
|||
@bacpypes_debugging
|
||||
class ServerSSM(SSM):
|
||||
|
||||
def __init__(self, sap):
|
||||
if _debug: ServerSSM._debug("__init__ %s", sap)
|
||||
SSM.__init__(self, sap)
|
||||
def __init__(self, sap, localDevice, remoteDevice):
|
||||
if _debug: ServerSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
|
||||
SSM.__init__(self, sap, localDevice, remoteDevice)
|
||||
|
||||
def set_state(self, newState, timer=0):
|
||||
"""This function is called when the client wants to change state."""
|
||||
|
@ -684,8 +661,15 @@ class ServerSSM(SSM):
|
|||
|
||||
# completed or aborted, remove tracking
|
||||
if (newState == COMPLETED) or (newState == ABORTED):
|
||||
if _debug: ServerSSM._debug(" - remove from active transactions")
|
||||
self.ssmSAP.serverTransactions.remove(self)
|
||||
|
||||
if _debug: ServerSSM._debug(" - release device information")
|
||||
self.ssmSAP.deviceInfoCache.release_device_info(self.remoteDevice)
|
||||
|
||||
# help along the garbage collector
|
||||
self.ssmSAP = self.localDevice = self.remoteDevice = None
|
||||
|
||||
def request(self, apdu):
|
||||
"""This function is called by transaction functions to send
|
||||
to the application."""
|
||||
|
@ -704,7 +688,7 @@ class ServerSSM(SSM):
|
|||
if _debug: ServerSSM._debug("indication %r", apdu)
|
||||
|
||||
if self.state == IDLE:
|
||||
self.Idle(apdu)
|
||||
self.idle(apdu)
|
||||
elif self.state == SEGMENTED_REQUEST:
|
||||
self.segmented_request(apdu)
|
||||
elif self.state == AWAIT_RESPONSE:
|
||||
|
@ -783,19 +767,23 @@ class ServerSSM(SSM):
|
|||
|
||||
# make sure we support segmented transmit if we need to
|
||||
if self.segmentCount > 1:
|
||||
if _debug: ServerSSM._debug(" - segmentation required, %d segemnts", self.segmentCount)
|
||||
if _debug: ServerSSM._debug(" - segmentation required, %d segments", self.segmentCount)
|
||||
|
||||
if (self.ssmSAP.segmentationSupported != 'segmentedTransmit') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'):
|
||||
# make sure we support segmented transmit
|
||||
if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
|
||||
if _debug: ServerSSM._debug(" - server can't send segmented responses")
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.request(abort)
|
||||
return
|
||||
if (self.remoteDevice.segmentationSupported != 'segmentedReceive') and (self.remoteDevice.segmentationSupported != 'segmentedBoth'):
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.request(abort)
|
||||
self.reponse(abort)
|
||||
return
|
||||
|
||||
### check to make sure the client can receive that many
|
||||
### look at apduMaxSegs
|
||||
# make sure client supports segmented receive
|
||||
if self.remoteDevice.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
|
||||
if _debug: ServerSSM._debug(" - client can't receive segmented responses")
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.response(abort)
|
||||
return
|
||||
|
||||
### check for APDUTooLong?
|
||||
|
||||
# initialize the state
|
||||
self.segmentRetryCount = 0
|
||||
|
@ -846,8 +834,8 @@ class ServerSSM(SSM):
|
|||
# return an abort APDU
|
||||
return AbortPDU(True, self.invokeID, reason)
|
||||
|
||||
def Idle(self, apdu):
|
||||
if _debug: ServerSSM._debug("Idle %r", apdu)
|
||||
def idle(self, apdu):
|
||||
if _debug: ServerSSM._debug("idle %r", apdu)
|
||||
|
||||
# make sure we're getting confirmed requests
|
||||
if not isinstance(apdu, ConfirmedRequestPDU):
|
||||
|
@ -857,36 +845,29 @@ class ServerSSM(SSM):
|
|||
self.invokeID = apdu.apduInvokeID
|
||||
if _debug: ServerSSM._debug(" - invoke ID: %r", self.invokeID)
|
||||
|
||||
# get information about the client
|
||||
self.remoteDevice = remote_device = self.ssmSAP.get_device_info(apdu.pduSource)
|
||||
if _debug: ServerSSM._debug(" - remoteDevice: %r", self.remoteDevice)
|
||||
|
||||
# make sure the device information is synced with the request
|
||||
if apdu.apduSA:
|
||||
if remote_device.segmentationSupported == 'noSegmentation':
|
||||
if _debug: ServerSSM._debug(" - client supports segmented receive")
|
||||
remote_device.segmentationSupported = 'segmentedReceive'
|
||||
if self.remoteDevice.segmentationSupported == 'noSegmentation':
|
||||
if _debug: ServerSSM._debug(" - client actually supports segmented receive")
|
||||
self.remoteDevice.segmentationSupported = 'segmentedReceive'
|
||||
|
||||
elif remote_device.segmentationSupported == 'segmentedTransmit':
|
||||
if _debug: ServerSSM._debug(" - client supports both segmented transmit and receive")
|
||||
remote_device.segmentationSupported = 'segmentedBoth'
|
||||
elif self.remoteDevice.segmentationSupported == 'segmentedTransmit':
|
||||
if _debug: ServerSSM._debug(" - client actually supports both segmented transmit and receive")
|
||||
self.remoteDevice.segmentationSupported = 'segmentedBoth'
|
||||
|
||||
elif remote_device.segmentationSupported == 'segmentedReceive':
|
||||
elif self.remoteDevice.segmentationSupported == 'segmentedReceive':
|
||||
pass
|
||||
|
||||
elif remote_device.segmentationSupported == 'segmentedBoth':
|
||||
elif self.remoteDevice.segmentationSupported == 'segmentedBoth':
|
||||
pass
|
||||
|
||||
else:
|
||||
raise RuntimeError("invalid segmentation supported in device info")
|
||||
|
||||
if apdu.apduMaxSegs != remote_device.maxSegmentsAccepted:
|
||||
if _debug: ServerSSM._debug(" - updating maximum segments accepted")
|
||||
remote_device.maxSegmentsAccepted = apdu.apduMaxSegs
|
||||
|
||||
if apdu.apduMaxResp != remote_device.maxApduLengthAccepted:
|
||||
if _debug: ServerSSM._debug(" - updating maximum max APDU length accepted")
|
||||
remote_device.maxApduLengthAccepted = apdu.apduMaxResp
|
||||
if apdu.apduMaxSegs != self.remoteDevice.maxSegmentsAccepted:
|
||||
if _debug: ServerSSM._debug(" - update maximum segments accepted?")
|
||||
if apdu.apduMaxResp != self.remoteDevice.maxApduLengthAccepted:
|
||||
if _debug: ServerSSM._debug(" - update maximum max APDU length accepted?")
|
||||
|
||||
# save the number of segments the client is willing to accept in the ack
|
||||
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
||||
|
@ -898,7 +879,7 @@ class ServerSSM(SSM):
|
|||
return
|
||||
|
||||
# make sure we support segmented requests
|
||||
if (self.ssmSAP.segmentationSupported != 'segmentedReceive') and (self.ssmSAP.segmentationSupported != 'segmentedBoth'):
|
||||
if self.segmentationSupported not in ('segmentedReceive', 'segmentedBoth'):
|
||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||
self.response(abort)
|
||||
return
|
||||
|
@ -1073,31 +1054,34 @@ class ServerSSM(SSM):
|
|||
#
|
||||
|
||||
@bacpypes_debugging
|
||||
class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
||||
class StateMachineAccessPoint(Client, ServiceAccessPoint):
|
||||
|
||||
def __init__(self, device, sap=None, cid=None):
|
||||
if _debug: StateMachineAccessPoint._debug("__init__ %r sap=%r cid=%r", device, sap, cid)
|
||||
def __init__(self, localDevice=None, deviceInfoCache=None, sap=None, cid=None):
|
||||
if _debug: StateMachineAccessPoint._debug("__init__ localDevice=%r deviceInfoCache=%r sap=%r cid=%r", localDevice, deviceInfoCache, sap, cid)
|
||||
|
||||
# basic initialization
|
||||
DeviceInfo.__init__(self)
|
||||
Client.__init__(self, cid)
|
||||
ServiceAccessPoint.__init__(self, sap)
|
||||
|
||||
# device information from the device object
|
||||
self.segmentationSupported = device.segmentationSupported # normally no segmentation
|
||||
self.segmentTimeout = device.apduSegmentTimeout # how long to wait for a segAck
|
||||
self.maxApduLengthAccepted = device.maxApduLengthAccepted # how big to divide up apdu's
|
||||
self.maxSegmentsAccepted = device.maxSegmentsAccepted # limit on how many segments to recieve
|
||||
# save a reference to the local device object and the cache
|
||||
self.localDevice = localDevice
|
||||
self.deviceInfoCache = deviceInfoCache
|
||||
|
||||
# client settings
|
||||
self.clientTransactions = []
|
||||
self.retryCount = device.numberOfApduRetries # how many times to repeat the request
|
||||
self.retryTimeout = device.apduTimeout # how long between retrying the request
|
||||
self.nextInvokeID = 1
|
||||
self.clientTransactions = []
|
||||
|
||||
# server settings
|
||||
self.serverTransactions = []
|
||||
self.applicationTimeout = device.apduTimeout # how long the application has to respond
|
||||
|
||||
# segmentation settings
|
||||
self.retryTimeout = 3000
|
||||
self.segmentTimeout = 1500
|
||||
self.maxSegmentsAccepted = 8
|
||||
|
||||
# how long the state machine is willing to wait for the application
|
||||
# layer to form a response and send it
|
||||
self.applicationTimeout = 3000
|
||||
|
||||
def get_next_invoke_id(self, addr):
|
||||
"""Called by clients to get an unused invoke ID."""
|
||||
|
@ -1120,13 +1104,6 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
|||
|
||||
return invokeID
|
||||
|
||||
def get_device_info(self, addr):
|
||||
"""get the segmentation supported and max APDU length accepted for a device."""
|
||||
if _debug: StateMachineAccessPoint._debug("get_device_info %r", addr)
|
||||
|
||||
# return a generic info object
|
||||
return DeviceInfo(addr)
|
||||
|
||||
def confirmation(self, pdu):
|
||||
"""Packets coming up the stack are APDU's."""
|
||||
if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu)
|
||||
|
@ -1148,8 +1125,11 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
|||
if (apdu.pduSource == tr.remoteDevice.address) and (apdu.apduInvokeID == tr.invokeID):
|
||||
break
|
||||
else:
|
||||
# find the remote device information
|
||||
remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduSource)
|
||||
|
||||
# build a server transaction
|
||||
tr = ServerSSM(self)
|
||||
tr = ServerSSM(self, self.localDevice, remoteDevice)
|
||||
|
||||
# add it to our transactions to track it
|
||||
self.serverTransactions.append(tr)
|
||||
|
@ -1244,8 +1224,13 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
|||
if (apdu.pduDestination.addrType != Address.localStationAddr) and (apdu.pduDestination.addrType != Address.remoteStationAddr):
|
||||
StateMachineAccessPoint._warning("%s is not a local or remote station", apdu.pduDestination)
|
||||
|
||||
# find the remote device information
|
||||
remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduDestination)
|
||||
if _debug: StateMachineAccessPoint._debug(" - remoteDevice: %r", remoteDevice)
|
||||
|
||||
# create a client transaction state machine
|
||||
tr = ClientSSM(self)
|
||||
tr = ClientSSM(self, self.localDevice, remoteDevice)
|
||||
if _debug: StateMachineAccessPoint._debug(" - client segmentation state machine: %r", tr)
|
||||
|
||||
# add it to our transactions to track it
|
||||
self.clientTransactions.append(tr)
|
||||
|
@ -1359,6 +1344,12 @@ class ApplicationServiceAccessPoint(ApplicationServiceElement, ServiceAccessPoin
|
|||
# forward the encoded packet
|
||||
self.request(xpdu)
|
||||
|
||||
# if the upper layers of the application did not assign an invoke ID,
|
||||
# copy the one that was assigned on its way down the stack
|
||||
if isinstance(apdu, ConfirmedRequestPDU) and apdu.apduInvokeID is None:
|
||||
if _debug: ApplicationServiceAccessPoint._debug(" - pass invoke ID upstream %r", xpdu.apduInvokeID)
|
||||
apdu.apduInvokeID = xpdu.apduInvokeID
|
||||
|
||||
def confirmation(self, apdu):
|
||||
if _debug: ApplicationServiceAccessPoint._debug("confirmation %r", apdu)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user