1
0
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:
Joel Bender 2016-07-03 00:08:07 -04:00
parent 54a5104d35
commit f78a8c9d67
3 changed files with 284 additions and 129 deletions

View File

@ -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: {0}".format(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

View File

@ -4,10 +4,10 @@
Application Module Application Module
""" """
from .debugging import bacpypes_debugging, ModuleLogger 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
# #
@ -146,13 +292,19 @@ class LocalDeviceObject(DeviceObject):
@bacpypes_debugging @bacpypes_debugging
class Application(ApplicationServiceElement): 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
@ -610,9 +762,9 @@ class Application(ApplicationServiceElement):
@bacpypes_debugging @bacpypes_debugging
class BIPSimpleApplication(Application): 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()
@ -621,6 +773,10 @@ class BIPSimpleApplication(Application):
# 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()

View File

@ -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:
@ -783,19 +767,23 @@ class ServerSSM(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 _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) abort = self.abort(AbortReason.segmentationNotSupported)
self.request(abort) self.reponse(abort)
return
if (self.remoteDevice.segmentationSupported != 'segmentedReceive') and (self.remoteDevice.segmentationSupported != 'segmentedBoth'):
abort = self.abort(AbortReason.segmentationNotSupported)
self.request(abort)
return return
### check to make sure the client can receive that many # make sure client supports segmented receive
### look at apduMaxSegs 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 # initialize the state
self.segmentRetryCount = 0 self.segmentRetryCount = 0
@ -846,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):
@ -857,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
@ -898,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
@ -1073,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."""
@ -1120,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)
@ -1148,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)
@ -1244,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)
@ -1359,6 +1344,12 @@ class ApplicationServiceAccessPoint(ApplicationServiceElement, ServiceAccessPoin
# forward the encoded packet # forward the encoded packet
self.request(xpdu) 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): def confirmation(self, apdu):
if _debug: ApplicationServiceAccessPoint._debug("confirmation %r", apdu) if _debug: ApplicationServiceAccessPoint._debug("confirmation %r", apdu)