1
0
mirror of https://github.com/JoelBender/bacpypes synced 2025-09-28 22:15:23 +08:00
bacpypes/py25/bacpypes/app.py
2015-07-31 21:42:42 -04:00

670 lines
25 KiB
Python
Executable File

#!/usr/bin/python
"""
Application Module
"""
from .debugging import ModuleLogger, Logging
from .comm import ApplicationServiceElement, bind
from .pdu import Address
from .primitivedata import Atomic, Date, Null, ObjectIdentifier, Time, Unsigned
from .constructeddata import Any, Array, ArrayOf
from .appservice import StateMachineAccessPoint, ApplicationServiceAccessPoint
from .netservice import NetworkServiceAccessPoint, NetworkServiceElement
from .bvllservice import BIPSimple, BIPForeign, AnnexJCodec, UDPMultiplexer
from .object import Property, PropertyError, DeviceObject, \
registered_object_types, register_object_type
from .apdu import ConfirmedRequestPDU, SimpleAckPDU, RejectPDU, RejectReason
from .apdu import IAmRequest, ReadPropertyACK, Error
from .errors import ExecutionError
from .apdu import \
AtomicReadFileACK, \
AtomicReadFileACKAccessMethodChoice, \
AtomicReadFileACKAccessMethodRecordAccess, \
AtomicReadFileACKAccessMethodStreamAccess, \
AtomicWriteFileACK
# some debugging
_debug = 0
_log = ModuleLogger(globals())
#
# CurrentDateProperty
#
class CurrentDateProperty(Property):
def __init__(self, identifier):
Property.__init__(self, identifier, Date, default=None, optional=True, mutable=False)
def ReadProperty(self, obj, arrayIndex=None):
# access an array
if arrayIndex is not None:
raise TypeError("%r is unsubscriptable" % (self.identifier,))
# get the value
now = Date()
now.now()
return now.value
def WriteProperty(self, obj, value, arrayIndex=None, priority=None):
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')
#
# CurrentTimeProperty
#
class CurrentTimeProperty(Property):
def __init__(self, identifier):
Property.__init__(self, identifier, Time, default=None, optional=True, mutable=False)
def ReadProperty(self, obj, arrayIndex=None):
# access an array
if arrayIndex is not None:
raise TypeError("%r is unsubscriptable" % (self.identifier,))
# get the value
now = Time()
now.now()
return now.value
def WriteProperty(self, obj, value, arrayIndex=None, priority=None):
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')
#
# LocalDeviceObject
#
class LocalDeviceObject(DeviceObject, Logging):
properties = \
[ CurrentTimeProperty('localTime')
, CurrentDateProperty('localDate')
]
defaultProperties = \
{ 'maxApduLengthAccepted': 1024
, 'segmentationSupported': 'segmentedBoth'
, 'maxSegmentsAccepted': 16
, 'apduSegmentTimeout': 20000
, 'apduTimeout': 3000
, 'numberOfApduRetries': 3
}
def __init__(self, **kwargs):
if _debug: LocalDeviceObject._debug("__init__ %r", kwargs)
# fill in default property values not in kwargs
for attr, value in LocalDeviceObject.defaultProperties.items():
if attr not in kwargs:
kwargs[attr] = value
# check for registration
if self.__class__ not in registered_object_types.values():
if 'vendorIdentifier' not in kwargs:
raise RuntimeError("vendorIdentifier required to auto-register the LocalDeviceObject class")
register_object_type(self.__class__, vendor_id=kwargs['vendorIdentifier'])
# check for local time
if 'localDate' in kwargs:
raise RuntimeError("localDate is provided by LocalDeviceObject and cannot be overridden")
if 'localTime' in kwargs:
raise RuntimeError("localTime is provided by LocalDeviceObject and cannot be overridden")
# proceed as usual
DeviceObject.__init__(self, **kwargs)
# create a default implementation of an object list for local devices.
# If it is specified in the kwargs, that overrides this default.
if ('objectList' not in kwargs):
self.objectList = ArrayOf(ObjectIdentifier)([self.objectIdentifier])
# if the object has a property list and one wasn't provided
# in the kwargs, then it was created by default and the objectList
# property should be included
if ('propertyList' not in kwargs) and self.propertyList:
# make sure it's not already there
if 'objectList' not in self.propertyList:
self.propertyList.append('objectList')
#
# Application
#
class Application(ApplicationServiceElement, Logging):
def __init__(self, localDevice, localAddress, aseID=None):
if _debug: Application._debug("__init__ %r %r aseID=%r", localDevice, localAddress, aseID)
ApplicationServiceElement.__init__(self, aseID)
# keep track of the local device
self.localDevice = localDevice
# allow the address to be cast to the correct type
if isinstance(localAddress, Address):
self.localAddress = localAddress
else:
self.localAddress = Address(localAddress)
# local objects by ID and name
self.objectName = {localDevice.objectName:localDevice}
self.objectIdentifier = {localDevice.objectIdentifier:localDevice}
def add_object(self, obj):
"""Add an object to the local collection."""
if _debug: Application._debug("add_object %r", obj)
# extract the object name and identifier
object_name = obj.objectName
if not object_name:
raise RuntimeError("object name required")
object_identifier = obj.objectIdentifier
if not object_identifier:
raise RuntimeError("object identifier required")
# make sure it hasn't already been defined
if object_name in self.objectName:
raise RuntimeError("already an object with name %r" % (object_name,))
if object_identifier in self.objectIdentifier:
raise RuntimeError("already an object with identifier %r" % (object_identifier,))
# now put it in local dictionaries
self.objectName[object_name] = obj
self.objectIdentifier[object_identifier] = obj
# append the new object's identifier to the device's object list
self.localDevice.objectList.append(object_identifier)
def delete_object(self, obj):
"""Add an object to the local collection."""
if _debug: Application._debug("delete_object %r", obj)
# extract the object name and identifier
object_name = obj.objectName
object_identifier = obj.objectIdentifier
# delete it from the application
del self.objectName[object_name]
del self.objectIdentifier[object_identifier]
# remove the object's identifier from the device's object list
indx = self.localDevice.objectList.index(object_identifier)
del self.localDevice.objectList[indx]
def get_object_id(self, objid):
"""Return a local object or None."""
return self.objectIdentifier.get(objid, None)
def get_object_name(self, objname):
"""Return a local object or None."""
return self.objectName.get(objname, None)
def iter_objects(self):
"""Iterate over the objects."""
return iter(self.objectIdentifier.values())
#-----
def indication(self, apdu):
if _debug: Application._debug("indication %r", apdu)
# get a helper function
helperName = "do_" + apdu.__class__.__name__
helperFn = getattr(self, helperName, None)
if _debug: Application._debug(" - helperFn: %r", helperFn)
# send back a reject for unrecognized services
if not helperFn:
if isinstance(apdu, ConfirmedRequestPDU):
response = RejectPDU( apdu.apduInvokeID, RejectReason.UNRECOGNIZEDSERVICE, context=apdu)
self.response(response)
return
# pass the apdu on to the helper function
try:
helperFn(apdu)
except ExecutionError, err:
if _debug: Application._debug(" - execution error: %r", err)
# send back an error
if isinstance(apdu, ConfirmedRequestPDU):
resp = Error(errorClass=err.errorClass, errorCode=err.errorCode, context=apdu)
self.response(resp)
except Exception, err:
Application._exception("exception: %r", err)
# send back an error
if isinstance(apdu, ConfirmedRequestPDU):
resp = Error(errorClass='device', errorCode='operationalProblem', context=apdu)
self.response(resp)
def do_WhoIsRequest(self, apdu):
"""Respond to a Who-Is request."""
if _debug: Application._debug("do_WhoIsRequest %r", apdu)
# may be a restriction
if (apdu.deviceInstanceRangeLowLimit is not None) and \
(apdu.deviceInstanceRangeHighLimit is not None):
if (self.localDevice.objectIdentifier[1] < apdu.deviceInstanceRangeLowLimit):
return
if (self.localDevice.objectIdentifier[1] > apdu.deviceInstanceRangeHighLimit):
return
# create a I-Am "response" back to the source
iAm = IAmRequest()
iAm.pduDestination = apdu.pduSource
iAm.iAmDeviceIdentifier = self.localDevice.objectIdentifier
iAm.maxAPDULengthAccepted = self.localDevice.maxApduLengthAccepted
iAm.segmentationSupported = self.localDevice.segmentationSupported
iAm.vendorID = self.localDevice.vendorIdentifier
if _debug: Application._debug(" - iAm: %r", iAm)
# away it goes
self.request(iAm)
def do_ReadPropertyRequest(self, apdu):
"""Return the value of some property of one of our objects."""
if _debug: Application._debug("do_ReadPropertyRequest %r", apdu)
# extract the object identifier
objId = apdu.objectIdentifier
# check for wildcard
if (objId == ('device', 4194303)):
if _debug: Application._debug(" - wildcard device identifier")
objId = self.localDevice.objectIdentifier
# get the object
obj = self.get_object_id(objId)
if _debug: Application._debug(" - object: %r", obj)
if not obj:
resp = Error(errorClass='object', errorCode='unknownObject', context=apdu)
else:
try:
# get the datatype
datatype = obj.get_datatype(apdu.propertyIdentifier)
if _debug: Application._debug(" - datatype: %r", datatype)
# get the value
value = obj.ReadProperty(apdu.propertyIdentifier, apdu.propertyArrayIndex)
if _debug: Application._debug(" - value: %r", value)
if value is None:
raise PropertyError(apdu.propertyIdentifier)
# change atomic values into something encodeable
if issubclass(datatype, Atomic):
value = datatype(value)
elif issubclass(datatype, Array) and (apdu.propertyArrayIndex is not None):
if apdu.propertyArrayIndex == 0:
value = Unsigned(value)
elif issubclass(datatype.subtype, Atomic):
value = datatype.subtype(value)
elif not isinstance(value, datatype.subtype):
raise TypeError("invalid result datatype, expecting %r and got %r" \
% (datatype.subtype.__name__, type(value).__name__))
elif not isinstance(value, datatype):
raise TypeError("invalid result datatype, expecting %r and got %r" \
% (datatype.__name__, type(value).__name__))
if _debug: Application._debug(" - encodeable value: %r", value)
# this is a ReadProperty ack
resp = ReadPropertyACK(context=apdu)
resp.objectIdentifier = objId
resp.propertyIdentifier = apdu.propertyIdentifier
resp.propertyArrayIndex = apdu.propertyArrayIndex
# save the result in the property value
resp.propertyValue = Any()
resp.propertyValue.cast_in(value)
except PropertyError:
resp = Error(errorClass='object', errorCode='unknownProperty', context=apdu)
if _debug: Application._debug(" - resp: %r", resp)
# return the result
self.response(resp)
def do_WritePropertyRequest(self, apdu):
"""Change the value of some property of one of our objects."""
if _debug: Application._debug("do_WritePropertyRequest %r", apdu)
# get the object
obj = self.get_object_id(apdu.objectIdentifier)
if _debug: Application._debug(" - object: %r", obj)
if not obj:
resp = Error(errorClass='object', errorCode='unknownObject', context=apdu)
else:
try:
# check if the property exists
if obj.ReadProperty(apdu.propertyIdentifier, apdu.propertyArrayIndex) is None:
raise PropertyError(apdu.propertyIdentifier)
# get the datatype, special case for null
if apdu.propertyValue.is_application_class_null():
datatype = Null
else:
datatype = obj.get_datatype(apdu.propertyIdentifier)
if _debug: Application._debug(" - datatype: %r", datatype)
# special case for array parts, others are managed by cast_out
if issubclass(datatype, Array) and (apdu.propertyArrayIndex is not None):
if apdu.propertyArrayIndex == 0:
value = apdu.propertyValue.cast_out(Unsigned)
else:
value = apdu.propertyValue.cast_out(datatype.subtype)
else:
value = apdu.propertyValue.cast_out(datatype)
if _debug: Application._debug(" - value: %r", value)
# change the value
value = obj.WriteProperty(apdu.propertyIdentifier, value, apdu.propertyArrayIndex, apdu.priority)
# success
resp = SimpleAckPDU(context=apdu)
except PropertyError:
resp = Error(errorClass='object', errorCode='unknownProperty', context=apdu)
if _debug: Application._debug(" - resp: %r", resp)
# return the result
self.response(resp)
def do_AtomicReadFileRequest(self, apdu):
"""Return one of our records."""
if _debug: Application._debug("do_AtomicReadFileRequest %r", apdu)
if (apdu.fileIdentifier[0] != 'file'):
resp = Error(errorClass='services', errorCode='inconsistentObjectType', context=apdu)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# get the object
obj = self.get_object_id(apdu.fileIdentifier)
if _debug: Application._debug(" - object: %r", obj)
if not obj:
resp = Error(errorClass='object', errorCode='unknownObject', context=apdu)
elif apdu.accessMethod.recordAccess:
# check against the object
if obj.fileAccessMethod != 'recordAccess':
resp = Error(errorClass='services',
errorCode='invalidFileAccessMethod',
context=apdu
)
### verify start is valid - double check this (empty files?)
elif (apdu.accessMethod.recordAccess.fileStartRecord < 0) or \
(apdu.accessMethod.recordAccess.fileStartRecord >= len(obj)):
resp = Error(errorClass='services',
errorCode='invalidFileStartPosition',
context=apdu
)
else:
# pass along to the object
end_of_file, record_data = obj.ReadFile(
apdu.accessMethod.recordAccess.fileStartRecord,
apdu.accessMethod.recordAccess.requestedRecordCount,
)
if _debug: Application._debug(" - record_data: %r", record_data)
# this is an ack
resp = AtomicReadFileACK(context=apdu,
endOfFile=end_of_file,
accessMethod=AtomicReadFileACKAccessMethodChoice(
recordAccess=AtomicReadFileACKAccessMethodRecordAccess(
fileStartRecord=apdu.accessMethod.recordAccess.fileStartRecord,
returnedRecordCount=len(record_data),
fileRecordData=record_data,
),
),
)
elif apdu.accessMethod.streamAccess:
# check against the object
if obj.fileAccessMethod != 'streamAccess':
resp = Error(errorClass='services',
errorCode='invalidFileAccessMethod',
context=apdu
)
### verify start is valid - double check this (empty files?)
elif (apdu.accessMethod.streamAccess.fileStartPosition < 0) or \
(apdu.accessMethod.streamAccess.fileStartPosition >= len(obj)):
resp = Error(errorClass='services',
errorCode='invalidFileStartPosition',
context=apdu
)
else:
# pass along to the object
end_of_file, record_data = obj.ReadFile(
apdu.accessMethod.streamAccess.fileStartPosition,
apdu.accessMethod.streamAccess.requestedOctetCount,
)
if _debug: Application._debug(" - record_data: %r", record_data)
# this is an ack
resp = AtomicReadFileACK(context=apdu,
endOfFile=end_of_file,
accessMethod=AtomicReadFileACKAccessMethodChoice(
streamAccess=AtomicReadFileACKAccessMethodStreamAccess(
fileStartPosition=apdu.accessMethod.streamAccess.fileStartPosition,
fileData=record_data,
),
),
)
if _debug: Application._debug(" - resp: %r", resp)
# return the result
self.response(resp)
def do_AtomicWriteFileRequest(self, apdu):
"""Return one of our records."""
if _debug: Application._debug("do_AtomicWriteFileRequest %r", apdu)
if (apdu.fileIdentifier[0] != 'file'):
resp = Error(errorClass='services', errorCode='inconsistentObjectType', context=apdu)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# get the object
obj = self.get_object_id(apdu.fileIdentifier)
if _debug: Application._debug(" - object: %r", obj)
if not obj:
resp = Error(errorClass='object', errorCode='unknownObject', context=apdu)
elif apdu.accessMethod.recordAccess:
# check against the object
if obj.fileAccessMethod != 'recordAccess':
resp = Error(errorClass='services',
errorCode='invalidFileAccessMethod',
context=apdu
)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# check for read-only
if obj.readOnly:
resp = Error(errorClass='services',
errorCode='fileAccessDenied',
context=apdu
)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# pass along to the object
start_record = obj.WriteFile(
apdu.accessMethod.recordAccess.fileStartRecord,
apdu.accessMethod.recordAccess.recordCount,
apdu.accessMethod.recordAccess.fileRecordData,
)
if _debug: Application._debug(" - start_record: %r", start_record)
# this is an ack
resp = AtomicWriteFileACK(context=apdu,
fileStartRecord=start_record,
)
elif apdu.accessMethod.streamAccess:
# check against the object
if obj.fileAccessMethod != 'streamAccess':
resp = Error(errorClass='services',
errorCode='invalidFileAccessMethod',
context=apdu
)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# check for read-only
if obj.readOnly:
resp = Error(errorClass='services',
errorCode='fileAccessDenied',
context=apdu
)
if _debug: Application._debug(" - error resp: %r", resp)
self.response(resp)
return
# pass along to the object
start_position = obj.WriteFile(
apdu.accessMethod.streamAccess.fileStartPosition,
apdu.accessMethod.streamAccess.fileData,
)
if _debug: Application._debug(" - start_position: %r", start_position)
# this is an ack
resp = AtomicWriteFileACK(context=apdu,
fileStartPosition=start_position,
)
if _debug: Application._debug(" - resp: %r", resp)
# return the result
self.response(resp)
#
# BIPSimpleApplication
#
class BIPSimpleApplication(Application, Logging):
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)
# include a application decoder
self.asap = ApplicationServiceAccessPoint()
# pass the device object to the state machine access point so it
# can know if it should support segmentation
self.smap = StateMachineAccessPoint(localDevice)
# a network service access point will be needed
self.nsap = NetworkServiceAccessPoint()
# give the NSAP a generic network layer service element
self.nse = NetworkServiceElement()
bind(self.nse, self.nsap)
# bind the top layers
bind(self, self.asap, self.smap, self.nsap)
# create a generic BIP stack, bound to the Annex J server
# on the UDP multiplexer
self.bip = BIPSimple()
self.annexj = AnnexJCodec()
self.mux = UDPMultiplexer(self.localAddress)
# bind the bottom layers
bind(self.bip, self.annexj, self.mux.annexJ)
# bind the BIP stack to the network, no network number
self.nsap.bind(self.bip)
#
# BIPForeignApplication
#
class BIPForeignApplication(Application, Logging):
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)
Application.__init__(self, localDevice, localAddress, aseID)
# include a application decoder
self.asap = ApplicationServiceAccessPoint()
# pass the device object to the state machine access point so it
# can know if it should support segmentation
self.smap = StateMachineAccessPoint(localDevice)
# a network service access point will be needed
self.nsap = NetworkServiceAccessPoint()
# give the NSAP a generic network layer service element
self.nse = NetworkServiceElement()
bind(self.nse, self.nsap)
# bind the top layers
bind(self, self.asap, self.smap, self.nsap)
# create a generic BIP stack, bound to the Annex J server
# on the UDP multiplexer
self.bip = BIPForeign(bbmdAddress, bbmdTTL)
self.annexj = AnnexJCodec()
self.mux = UDPMultiplexer(self.localAddress, noBroadcast=True)
# bind the bottom layers
bind(self.bip, self.annexj, self.mux.annexJ)
# bind the NSAP to the stack, no network number
self.nsap.bind(self.bip)
#
# BIPNetworkApplication
#
class BIPNetworkApplication(NetworkServiceElement, Logging):
def __init__(self, localAddress, eID=None):
if _debug: BIPNetworkApplication._debug("__init__ %r eID=%r", localAddress, eID)
NetworkServiceElement.__init__(self, eID)
# allow the address to be cast to the correct type
if isinstance(localAddress, Address):
self.localAddress = localAddress
else:
self.localAddress = Address(localAddress)
# a network service access point will be needed
self.nsap = NetworkServiceAccessPoint()
# give the NSAP a generic network layer service element
bind(self, self.nsap)
# create a generic BIP stack, bound to the Annex J server
# on the UDP multiplexer
self.bip = BIPSimple()
self.annexj = AnnexJCodec()
self.mux = UDPMultiplexer(self.localAddress)
# bind the bottom layers
bind(self.bip, self.annexj, self.mux.annexJ)
# bind the NSAP to the stack, no network number
self.nsap.bind(self.bip)