mirror of
https://github.com/JoelBender/bacpypes
synced 2025-09-28 22:15:23 +08:00
moving around functionality -- needs more testing, then sync with other python versions
This commit is contained in:
parent
4e92e6908f
commit
38f0c5e441
|
@ -68,11 +68,19 @@ def decode_max_apdu_segments(arg):
|
||||||
# encode_max_apdu_response/decode_max_apdu_response
|
# 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):
|
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: %d" % (arg,))
|
||||||
|
|
||||||
def decode_max_apdu_response(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
|
# APCI
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
Application Module
|
Application Module
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .debugging import ModuleLogger, Logging
|
from .debugging import bacpypes_debugging, DebugContents, ModuleLogger
|
||||||
from .comm import ApplicationServiceElement, bind
|
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 .primitivedata import Atomic, Date, Null, ObjectIdentifier, Time, Unsigned
|
||||||
from .constructeddata import Any, Array, ArrayOf
|
from .constructeddata import Any, Array, ArrayOf
|
||||||
|
@ -38,6 +38,152 @@ from .apdu import \
|
||||||
_debug = 0
|
_debug = 0
|
||||||
_log = ModuleLogger(globals())
|
_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
|
# CurrentDateProperty
|
||||||
#
|
#
|
||||||
|
@ -86,7 +232,8 @@ class CurrentTimeProperty(Property):
|
||||||
# LocalDeviceObject
|
# LocalDeviceObject
|
||||||
#
|
#
|
||||||
|
|
||||||
class LocalDeviceObject(DeviceObject, Logging):
|
@bacpypes_debugging
|
||||||
|
class LocalDeviceObject(DeviceObject):
|
||||||
|
|
||||||
properties = \
|
properties = \
|
||||||
[ CurrentTimeProperty('localTime')
|
[ CurrentTimeProperty('localTime')
|
||||||
|
@ -142,15 +289,22 @@ class LocalDeviceObject(DeviceObject, Logging):
|
||||||
# Application
|
# Application
|
||||||
#
|
#
|
||||||
|
|
||||||
class Application(ApplicationServiceElement, Logging):
|
@bacpypes_debugging
|
||||||
|
class Application(ApplicationServiceElement):
|
||||||
|
|
||||||
def __init__(self, localDevice, localAddress, aseID=None):
|
def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None):
|
||||||
if _debug: Application._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID)
|
if _debug: Application._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID)
|
||||||
ApplicationServiceElement.__init__(self, aseID)
|
ApplicationServiceElement.__init__(self, aseID)
|
||||||
|
|
||||||
# keep track of the local device
|
# keep track of the local device
|
||||||
self.localDevice = localDevice
|
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
|
# bind the device object to this application
|
||||||
localDevice._app = self
|
localDevice._app = self
|
||||||
|
|
||||||
|
@ -605,11 +759,12 @@ class Application(ApplicationServiceElement, Logging):
|
||||||
# BIPSimpleApplication
|
# BIPSimpleApplication
|
||||||
#
|
#
|
||||||
|
|
||||||
class BIPSimpleApplication(Application, Logging):
|
@bacpypes_debugging
|
||||||
|
class BIPSimpleApplication(Application):
|
||||||
|
|
||||||
def __init__(self, localDevice, localAddress, aseID=None):
|
def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None):
|
||||||
if _debug: BIPSimpleApplication._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID)
|
if _debug: BIPSimpleApplication._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID)
|
||||||
Application.__init__(self, localDevice, localAddress, aseID)
|
Application.__init__(self, localDevice, localAddress, deviceInfoCache, aseID)
|
||||||
|
|
||||||
# include a application decoder
|
# include a application decoder
|
||||||
self.asap = ApplicationServiceAccessPoint()
|
self.asap = ApplicationServiceAccessPoint()
|
||||||
|
@ -618,6 +773,10 @@ class BIPSimpleApplication(Application, Logging):
|
||||||
# can know if it should support segmentation
|
# can know if it should support segmentation
|
||||||
self.smap = StateMachineAccessPoint(localDevice)
|
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
|
# a network service access point will be needed
|
||||||
self.nsap = NetworkServiceAccessPoint()
|
self.nsap = NetworkServiceAccessPoint()
|
||||||
|
|
||||||
|
@ -644,7 +803,8 @@ class BIPSimpleApplication(Application, Logging):
|
||||||
# BIPForeignApplication
|
# BIPForeignApplication
|
||||||
#
|
#
|
||||||
|
|
||||||
class BIPForeignApplication(Application, Logging):
|
@bacpypes_debugging
|
||||||
|
class BIPForeignApplication(Application):
|
||||||
|
|
||||||
def __init__(self, localDevice, localAddress, bbmdAddress, bbmdTTL, aseID=None):
|
def __init__(self, localDevice, localAddress, bbmdAddress, bbmdTTL, aseID=None):
|
||||||
if _debug: BIPForeignApplication._debug("__init__ %r %r %r %r aseID=%r", localDevice, localAddress, bbmdAddress, bbmdTTL, aseID)
|
if _debug: BIPForeignApplication._debug("__init__ %r %r %r %r aseID=%r", localDevice, localAddress, bbmdAddress, bbmdTTL, aseID)
|
||||||
|
@ -683,7 +843,8 @@ class BIPForeignApplication(Application, Logging):
|
||||||
# BIPNetworkApplication
|
# BIPNetworkApplication
|
||||||
#
|
#
|
||||||
|
|
||||||
class BIPNetworkApplication(NetworkServiceElement, Logging):
|
@bacpypes_debugging
|
||||||
|
class BIPNetworkApplication(NetworkServiceElement):
|
||||||
|
|
||||||
def __init__(self, localAddress, eID=None):
|
def __init__(self, localAddress, eID=None):
|
||||||
if _debug: BIPNetworkApplication._debug("__init__ %r eID=%r", localAddress, eID)
|
if _debug: BIPNetworkApplication._debug("__init__ %r eID=%r", localAddress, eID)
|
||||||
|
|
|
@ -22,38 +22,6 @@ from .apdu import AbortPDU, AbortReason, ComplexAckPDU, \
|
||||||
_debug = 0
|
_debug = 0
|
||||||
_log = ModuleLogger(globals())
|
_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
|
# SSM - Segmentation State Machine
|
||||||
#
|
#
|
||||||
|
@ -76,26 +44,27 @@ class SSM(OneShotTask, DebugContents):
|
||||||
, 'SEGMENTED_RESPONSE', 'SEGMENTED_CONFIRMATION', 'COMPLETED', 'ABORTED'
|
, 'SEGMENTED_RESPONSE', 'SEGMENTED_CONFIRMATION', 'COMPLETED', 'ABORTED'
|
||||||
]
|
]
|
||||||
|
|
||||||
_debug_contents = ('ssmSAP', 'remoteDevice', 'invokeID'
|
_debug_contents = ('ssmSAP', 'localDevice', 'remoteDevice', 'invokeID'
|
||||||
, 'state', 'segmentAPDU', 'segmentSize', 'segmentCount', 'maxSegmentsAccepted'
|
, 'state', 'segmentAPDU', 'segmentSize', 'segmentCount', 'maxSegmentsAccepted'
|
||||||
, 'retryCount', 'segmentRetryCount', 'sentAllSegments', 'lastSequenceNumber'
|
, 'retryCount', 'segmentRetryCount', 'sentAllSegments', 'lastSequenceNumber'
|
||||||
, 'initialSequenceNumber', 'actualWindowSize', 'proposedWindowSize'
|
, 'initialSequenceNumber', 'actualWindowSize', 'proposedWindowSize'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, sap):
|
def __init__(self, sap, localDevice, remoteDevice):
|
||||||
"""Common parts for client and server segmentation."""
|
"""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)
|
OneShotTask.__init__(self)
|
||||||
|
|
||||||
self.ssmSAP = sap # reference to the service access point
|
self.ssmSAP = sap # service access point
|
||||||
self.remoteDevice = None # remote device
|
self.localDevice = localDevice # local device information, DeviceObject
|
||||||
|
self.remoteDevice = remoteDevice # remote device information, a DeviceInfo instance
|
||||||
self.invokeID = None # invoke ID
|
self.invokeID = None # invoke ID
|
||||||
|
|
||||||
self.state = IDLE # initial state
|
self.state = IDLE # initial state
|
||||||
self.segmentAPDU = None # refers to request or response
|
self.segmentAPDU = None # refers to request or response
|
||||||
self.segmentSize = None # how big the pieces are
|
self.segmentSize = None # how big the pieces are
|
||||||
self.segmentCount = None
|
self.segmentCount = None
|
||||||
self.maxSegmentsAccepted = None # maximum number of segments client will accept
|
self.maxSegmentsAccepted = None # maximum number of segments
|
||||||
|
|
||||||
self.retryCount = None
|
self.retryCount = None
|
||||||
self.segmentRetryCount = None
|
self.segmentRetryCount = None
|
||||||
|
@ -105,6 +74,14 @@ class SSM(OneShotTask, DebugContents):
|
||||||
self.actualWindowSize = None
|
self.actualWindowSize = None
|
||||||
self.proposedWindowSize = 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):
|
def start_timer(self, msecs):
|
||||||
if _debug: SSM._debug("start_timer %r", msecs)
|
if _debug: SSM._debug("start_timer %r", msecs)
|
||||||
|
|
||||||
|
@ -179,15 +156,12 @@ class SSM(OneShotTask, DebugContents):
|
||||||
segAPDU = ConfirmedRequestPDU(self.segmentAPDU.apduService)
|
segAPDU = ConfirmedRequestPDU(self.segmentAPDU.apduService)
|
||||||
|
|
||||||
segAPDU.apduMaxSegs = self.maxSegmentsAccepted
|
segAPDU.apduMaxSegs = self.maxSegmentsAccepted
|
||||||
segAPDU.apduMaxResp = self.ssmSAP.maxApduLengthAccepted
|
segAPDU.apduMaxResp = self.maxApduLengthAccepted
|
||||||
segAPDU.apduInvokeID = self.invokeID;
|
segAPDU.apduInvokeID = self.invokeID;
|
||||||
|
|
||||||
# segmented response accepted?
|
# segmented response accepted?
|
||||||
segAPDU.apduSA = ((self.ssmSAP.segmentationSupported == 'segmentedBoth') \
|
segAPDU.apduSA = self.segmentationSupported in ('segmentedReceive', 'segmentedBoth')
|
||||||
or (self.ssmSAP.segmentationSupported == 'segmentedReceive'))
|
if _debug: SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA)
|
||||||
if _debug:
|
|
||||||
SSM._debug(" - segmented response accepted: %r", segAPDU.apduSA)
|
|
||||||
SSM._debug(" - self.ssmSAP.segmentationSupported: %r", self.ssmSAP.segmentationSupported)
|
|
||||||
|
|
||||||
elif self.segmentAPDU.apduType == ComplexAckPDU.pduType:
|
elif self.segmentAPDU.apduType == ComplexAckPDU.pduType:
|
||||||
if _debug: SSM._debug(" - complex ack context")
|
if _debug: SSM._debug(" - complex ack context")
|
||||||
|
@ -262,9 +236,9 @@ class SSM(OneShotTask, DebugContents):
|
||||||
@bacpypes_debugging
|
@bacpypes_debugging
|
||||||
class ClientSSM(SSM):
|
class ClientSSM(SSM):
|
||||||
|
|
||||||
def __init__(self, sap):
|
def __init__(self, sap, localDevice, remoteDevice):
|
||||||
if _debug: ClientSSM._debug("__init__ %s", sap)
|
if _debug: ClientSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
|
||||||
SSM.__init__(self, sap)
|
SSM.__init__(self, sap, localDevice, remoteDevice)
|
||||||
|
|
||||||
# initialize the retry count
|
# initialize the retry count
|
||||||
self.retryCount = 0
|
self.retryCount = 0
|
||||||
|
@ -278,8 +252,15 @@ class ClientSSM(SSM):
|
||||||
|
|
||||||
# completed or aborted, remove tracking
|
# completed or aborted, remove tracking
|
||||||
if (newState == COMPLETED) or (newState == ABORTED):
|
if (newState == COMPLETED) or (newState == ABORTED):
|
||||||
|
if _debug: ClientSSM._debug(" - remove from active transactions")
|
||||||
self.ssmSAP.clientTransactions.remove(self)
|
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):
|
def request(self, apdu):
|
||||||
"""This function is called by client transaction functions when it wants
|
"""This function is called by client transaction functions when it wants
|
||||||
to send a message to the device."""
|
to send a message to the device."""
|
||||||
|
@ -304,24 +285,20 @@ class ClientSSM(SSM):
|
||||||
# save the request and set the segmentation context
|
# save the request and set the segmentation context
|
||||||
self.set_segmentation_context(apdu)
|
self.set_segmentation_context(apdu)
|
||||||
|
|
||||||
# get information about the device
|
# the segment size is the minimum of the maximum size I can transmit,
|
||||||
self.remoteDevice = self.ssmSAP.get_device_info(apdu.pduDestination)
|
# the maximum conveyable by the internetwork to the remote device, and
|
||||||
if _debug: ClientSSM._debug(" - remoteDevice: %r", self.remoteDevice)
|
# the maximum APDU size accepted by the remote device.
|
||||||
|
self.segmentSize = min(
|
||||||
# the segment size is the minimum of the maximum size I can transmit
|
self.maxApduLengthAccepted,
|
||||||
# (assumed to have no local buffer limitations), the maximum conveyable
|
self.remoteDevice.maxNpduLength,
|
||||||
# by the internetwork to the remote device, and the maximum APDU size
|
self.remoteDevice.maxApduLengthAccepted,
|
||||||
# accepted by the remote device.
|
)
|
||||||
self.segmentSize = min(self.remoteDevice.maxNpduLength, self.remoteDevice.maxApduLengthAccepted)
|
|
||||||
if _debug: ClientSSM._debug(" - segment size: %r", self.segmentSize)
|
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:
|
if apdu.apduMaxSegs is not None:
|
||||||
# this request overrides the default
|
# this request overrides the default
|
||||||
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
||||||
else:
|
|
||||||
# use the default in the device definition
|
|
||||||
self.maxSegmentsAccepted = self.ssmSAP.maxSegmentsAccepted
|
|
||||||
|
|
||||||
# save the invoke ID
|
# save the invoke ID
|
||||||
self.invokeID = apdu.apduInvokeID
|
self.invokeID = apdu.apduInvokeID
|
||||||
|
@ -340,12 +317,12 @@ class ClientSSM(SSM):
|
||||||
|
|
||||||
# make sure we support segmented transmit if we need to
|
# make sure we support segmented transmit if we need to
|
||||||
if self.segmentCount > 1:
|
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")
|
if _debug: ClientSSM._debug(" - local device can't send segmented requests")
|
||||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||||
self.response(abort)
|
self.response(abort)
|
||||||
return
|
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")
|
if _debug: ClientSSM._debug(" - remote device can't receive segmented requests")
|
||||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||||
self.response(abort)
|
self.response(abort)
|
||||||
|
@ -544,7 +521,7 @@ class ClientSSM(SSM):
|
||||||
self.set_state(COMPLETED)
|
self.set_state(COMPLETED)
|
||||||
self.response(apdu)
|
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")
|
if _debug: ClientSSM._debug(" - local device can't receive segmented messages")
|
||||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||||
self.response(abort)
|
self.response(abort)
|
||||||
|
@ -671,9 +648,9 @@ class ClientSSM(SSM):
|
||||||
@bacpypes_debugging
|
@bacpypes_debugging
|
||||||
class ServerSSM(SSM):
|
class ServerSSM(SSM):
|
||||||
|
|
||||||
def __init__(self, sap):
|
def __init__(self, sap, localDevice, remoteDevice):
|
||||||
if _debug: ServerSSM._debug("__init__ %s", sap)
|
if _debug: ServerSSM._debug("__init__ %s %r %r", sap, localDevice, remoteDevice)
|
||||||
SSM.__init__(self, sap)
|
SSM.__init__(self, sap, localDevice, remoteDevice)
|
||||||
|
|
||||||
def set_state(self, newState, timer=0):
|
def set_state(self, newState, timer=0):
|
||||||
"""This function is called when the client wants to change state."""
|
"""This function is called when the client wants to change state."""
|
||||||
|
@ -684,8 +661,15 @@ class ServerSSM(SSM):
|
||||||
|
|
||||||
# completed or aborted, remove tracking
|
# completed or aborted, remove tracking
|
||||||
if (newState == COMPLETED) or (newState == ABORTED):
|
if (newState == COMPLETED) or (newState == ABORTED):
|
||||||
|
if _debug: ServerSSM._debug(" - remove from active transactions")
|
||||||
self.ssmSAP.serverTransactions.remove(self)
|
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):
|
def request(self, apdu):
|
||||||
"""This function is called by transaction functions to send
|
"""This function is called by transaction functions to send
|
||||||
to the application."""
|
to the application."""
|
||||||
|
@ -704,7 +688,7 @@ class ServerSSM(SSM):
|
||||||
if _debug: ServerSSM._debug("indication %r", apdu)
|
if _debug: ServerSSM._debug("indication %r", apdu)
|
||||||
|
|
||||||
if self.state == IDLE:
|
if self.state == IDLE:
|
||||||
self.Idle(apdu)
|
self.idle(apdu)
|
||||||
elif self.state == SEGMENTED_REQUEST:
|
elif self.state == SEGMENTED_REQUEST:
|
||||||
self.segmented_request(apdu)
|
self.segmented_request(apdu)
|
||||||
elif self.state == AWAIT_RESPONSE:
|
elif self.state == AWAIT_RESPONSE:
|
||||||
|
@ -786,7 +770,7 @@ class ServerSSM(SSM):
|
||||||
if _debug: ServerSSM._debug(" - segmentation required, %d segments", self.segmentCount)
|
if _debug: ServerSSM._debug(" - segmentation required, %d segments", self.segmentCount)
|
||||||
|
|
||||||
# make sure we support segmented transmit
|
# make sure we support segmented transmit
|
||||||
if self.ssmSAP.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
|
if self.segmentationSupported not in ('segmentedTransmit', 'segmentedBoth'):
|
||||||
if _debug: ServerSSM._debug(" - server can't send segmented responses")
|
if _debug: ServerSSM._debug(" - server can't send segmented responses")
|
||||||
abort = self.abort(AbortReason.segmentationNotSupported)
|
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||||
self.reponse(abort)
|
self.reponse(abort)
|
||||||
|
@ -850,8 +834,8 @@ class ServerSSM(SSM):
|
||||||
# return an abort APDU
|
# return an abort APDU
|
||||||
return AbortPDU(True, self.invokeID, reason)
|
return AbortPDU(True, self.invokeID, reason)
|
||||||
|
|
||||||
def Idle(self, apdu):
|
def idle(self, apdu):
|
||||||
if _debug: ServerSSM._debug("Idle %r", apdu)
|
if _debug: ServerSSM._debug("idle %r", apdu)
|
||||||
|
|
||||||
# make sure we're getting confirmed requests
|
# make sure we're getting confirmed requests
|
||||||
if not isinstance(apdu, ConfirmedRequestPDU):
|
if not isinstance(apdu, ConfirmedRequestPDU):
|
||||||
|
@ -861,36 +845,29 @@ class ServerSSM(SSM):
|
||||||
self.invokeID = apdu.apduInvokeID
|
self.invokeID = apdu.apduInvokeID
|
||||||
if _debug: ServerSSM._debug(" - invoke ID: %r", self.invokeID)
|
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
|
# make sure the device information is synced with the request
|
||||||
if apdu.apduSA:
|
if apdu.apduSA:
|
||||||
if remote_device.segmentationSupported == 'noSegmentation':
|
if self.remoteDevice.segmentationSupported == 'noSegmentation':
|
||||||
if _debug: ServerSSM._debug(" - client supports segmented receive")
|
if _debug: ServerSSM._debug(" - client actually supports segmented receive")
|
||||||
remote_device.segmentationSupported = 'segmentedReceive'
|
self.remoteDevice.segmentationSupported = 'segmentedReceive'
|
||||||
|
|
||||||
elif remote_device.segmentationSupported == 'segmentedTransmit':
|
elif self.remoteDevice.segmentationSupported == 'segmentedTransmit':
|
||||||
if _debug: ServerSSM._debug(" - client supports both segmented transmit and receive")
|
if _debug: ServerSSM._debug(" - client actually supports both segmented transmit and receive")
|
||||||
remote_device.segmentationSupported = 'segmentedBoth'
|
self.remoteDevice.segmentationSupported = 'segmentedBoth'
|
||||||
|
|
||||||
elif remote_device.segmentationSupported == 'segmentedReceive':
|
elif self.remoteDevice.segmentationSupported == 'segmentedReceive':
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif remote_device.segmentationSupported == 'segmentedBoth':
|
elif self.remoteDevice.segmentationSupported == 'segmentedBoth':
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError("invalid segmentation supported in device info")
|
raise RuntimeError("invalid segmentation supported in device info")
|
||||||
|
|
||||||
if apdu.apduMaxSegs != remote_device.maxSegmentsAccepted:
|
if apdu.apduMaxSegs != self.remoteDevice.maxSegmentsAccepted:
|
||||||
if _debug: ServerSSM._debug(" - updating maximum segments accepted")
|
if _debug: ServerSSM._debug(" - update maximum segments accepted?")
|
||||||
remote_device.maxSegmentsAccepted = apdu.apduMaxSegs
|
if apdu.apduMaxResp != self.remoteDevice.maxApduLengthAccepted:
|
||||||
|
if _debug: ServerSSM._debug(" - update maximum max APDU length accepted?")
|
||||||
if apdu.apduMaxResp != remote_device.maxApduLengthAccepted:
|
|
||||||
if _debug: ServerSSM._debug(" - updating maximum max APDU length accepted")
|
|
||||||
remote_device.maxApduLengthAccepted = apdu.apduMaxResp
|
|
||||||
|
|
||||||
# save the number of segments the client is willing to accept in the ack
|
# save the number of segments the client is willing to accept in the ack
|
||||||
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
self.maxSegmentsAccepted = apdu.apduMaxSegs
|
||||||
|
@ -902,7 +879,7 @@ class ServerSSM(SSM):
|
||||||
return
|
return
|
||||||
|
|
||||||
# make sure we support segmented requests
|
# 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)
|
abort = self.abort(AbortReason.segmentationNotSupported)
|
||||||
self.response(abort)
|
self.response(abort)
|
||||||
return
|
return
|
||||||
|
@ -1077,31 +1054,34 @@ class ServerSSM(SSM):
|
||||||
#
|
#
|
||||||
|
|
||||||
@bacpypes_debugging
|
@bacpypes_debugging
|
||||||
class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
class StateMachineAccessPoint(Client, ServiceAccessPoint):
|
||||||
|
|
||||||
def __init__(self, device, sap=None, cid=None):
|
def __init__(self, localDevice=None, deviceInfoCache=None, sap=None, cid=None):
|
||||||
if _debug: StateMachineAccessPoint._debug("__init__ %r sap=%r cid=%r", device, sap, cid)
|
if _debug: StateMachineAccessPoint._debug("__init__ localDevice=%r deviceInfoCache=%r sap=%r cid=%r", localDevice, deviceInfoCache, sap, cid)
|
||||||
|
|
||||||
# basic initialization
|
# basic initialization
|
||||||
DeviceInfo.__init__(self)
|
|
||||||
Client.__init__(self, cid)
|
Client.__init__(self, cid)
|
||||||
ServiceAccessPoint.__init__(self, sap)
|
ServiceAccessPoint.__init__(self, sap)
|
||||||
|
|
||||||
# device information from the device object
|
# save a reference to the local device object and the cache
|
||||||
self.segmentationSupported = device.segmentationSupported # normally no segmentation
|
self.localDevice = localDevice
|
||||||
self.segmentTimeout = device.apduSegmentTimeout # how long to wait for a segAck
|
self.deviceInfoCache = deviceInfoCache
|
||||||
self.maxApduLengthAccepted = device.maxApduLengthAccepted # how big to divide up apdu's
|
|
||||||
self.maxSegmentsAccepted = device.maxSegmentsAccepted # limit on how many segments to recieve
|
|
||||||
|
|
||||||
# client settings
|
# 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.nextInvokeID = 1
|
||||||
|
self.clientTransactions = []
|
||||||
|
|
||||||
# server settings
|
# server settings
|
||||||
self.serverTransactions = []
|
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):
|
def get_next_invoke_id(self, addr):
|
||||||
"""Called by clients to get an unused invoke ID."""
|
"""Called by clients to get an unused invoke ID."""
|
||||||
|
@ -1124,13 +1104,6 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
||||||
|
|
||||||
return invokeID
|
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):
|
def confirmation(self, pdu):
|
||||||
"""Packets coming up the stack are APDU's."""
|
"""Packets coming up the stack are APDU's."""
|
||||||
if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu)
|
if _debug: StateMachineAccessPoint._debug("confirmation %r", pdu)
|
||||||
|
@ -1152,8 +1125,11 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
||||||
if (apdu.pduSource == tr.remoteDevice.address) and (apdu.apduInvokeID == tr.invokeID):
|
if (apdu.pduSource == tr.remoteDevice.address) and (apdu.apduInvokeID == tr.invokeID):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
# find the remote device information
|
||||||
|
remoteDevice = self.deviceInfoCache.get_device_info(apdu.pduSource)
|
||||||
|
|
||||||
# build a server transaction
|
# build a server transaction
|
||||||
tr = ServerSSM(self)
|
tr = ServerSSM(self, self.localDevice, remoteDevice)
|
||||||
|
|
||||||
# add it to our transactions to track it
|
# add it to our transactions to track it
|
||||||
self.serverTransactions.append(tr)
|
self.serverTransactions.append(tr)
|
||||||
|
@ -1248,8 +1224,13 @@ class StateMachineAccessPoint(DeviceInfo, Client, ServiceAccessPoint):
|
||||||
if (apdu.pduDestination.addrType != Address.localStationAddr) and (apdu.pduDestination.addrType != Address.remoteStationAddr):
|
if (apdu.pduDestination.addrType != Address.localStationAddr) and (apdu.pduDestination.addrType != Address.remoteStationAddr):
|
||||||
StateMachineAccessPoint._warning("%s is not a local or remote station", apdu.pduDestination)
|
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
|
# 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
|
# add it to our transactions to track it
|
||||||
self.clientTransactions.append(tr)
|
self.clientTransactions.append(tr)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user