diff --git a/py27/bacpypes/app.py b/py27/bacpypes/app.py index c1fd5b8..9daf95c 100755 --- a/py27/bacpypes/app.py +++ b/py27/bacpypes/app.py @@ -4,39 +4,33 @@ Application Module """ +import warnings + from .debugging import bacpypes_debugging, DebugContents, ModuleLogger from .comm import ApplicationServiceElement, bind -from .pdu import Address, LocalStation, RemoteStation +from .pdu import Address -from .primitivedata import Atomic, Date, Null, ObjectIdentifier, Time, Unsigned -from .constructeddata import Any, Array, ArrayOf +from .primitivedata import Date, Time, ObjectIdentifier +from .constructeddata import ArrayOf from .capability import Collector from .appservice import StateMachineAccessPoint, ApplicationServiceAccessPoint from .netservice import NetworkServiceAccessPoint, NetworkServiceElement from .bvllservice import BIPSimple, BIPForeign, AnnexJCodec, UDPMultiplexer -from .object import Property, PropertyError, DeviceObject, \ +from .object import Property, DeviceObject, \ registered_object_types, register_object_type -from .apdu import ConfirmedRequestPDU, SimpleAckPDU, RejectPDU, RejectReason -from .apdu import IAmRequest, ReadPropertyACK, Error -from .errors import ExecutionError, \ - RejectException, UnrecognizedService, MissingRequiredParameter, \ - ParameterOutOfRange, \ - AbortException +from .apdu import ConfirmedRequestPDU, Error +from .errors import ExecutionError, UnrecognizedService, AbortException, RejectException # for computing protocol services supported from .apdu import confirmed_request_types, unconfirmed_request_types, \ ConfirmedServiceChoice, UnconfirmedServiceChoice from .basetypes import ServicesSupported -from .apdu import \ - AtomicReadFileACK, \ - AtomicReadFileACKAccessMethodChoice, \ - AtomicReadFileACKAccessMethodRecordAccess, \ - AtomicReadFileACKAccessMethodStreamAccess, \ - AtomicWriteFileACK +# basic services +from .service.device import WhoIsIAmServices, ReadWritePropertyServices # some debugging _debug = 0 @@ -303,30 +297,42 @@ class LocalDeviceObject(DeviceObject): @bacpypes_debugging class Application(ApplicationServiceElement, Collector): - def __init__(self, localDevice, localAddress, deviceInfoCache=None, aseID=None): + def __init__(self, localDevice=None, localAddress=None, deviceInfoCache=None, aseID=None): if _debug: Application._debug("__init__ %r %r deviceInfoCache=%r aseID=%r", localDevice, localAddress, deviceInfoCache, aseID) ApplicationServiceElement.__init__(self, aseID) Collector.__init__(self) + # local objects by ID and name + self.objectName = {} + self.objectIdentifier = {} + # keep track of the local device - self.localDevice = localDevice + if localDevice: + self.localDevice = localDevice + + # bind the device object to this application + localDevice._app = self + + # local objects by ID and name + self.objectName[localDevice.objectName] = localDevice + self.objectIdentifier[localDevice.objectIdentifier] = localDevice + + # local address deprecated, but continue to use the old initializer + if localAddress is not None: + warnings.warn( + "local address at the application layer deprecated", + DeprecationWarning, + ) + + # allow the address to be cast to the correct type + if isinstance(localAddress, Address): + self.localAddress = localAddress + else: + self.localAddress = Address(localAddress) # use the provided cache or make a default one self.deviceInfoCache = deviceInfoCache or DeviceInfoCache() - # bind the device object to this application - localDevice._app = self - - # 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) @@ -353,8 +359,10 @@ class Application(ApplicationServiceElement, Collector): 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) + # append the new object's identifier to the local device's object list + # if there is one and it has an object list property + if self.localDevice and self.localDevice.objectList: + self.localDevice.objectList.append(object_identifier) # let the object know which application stack it belongs to obj._app = self @@ -372,8 +380,10 @@ class Application(ApplicationServiceElement, Collector): 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] + # if there is one and it has an object list property + if self.localDevice and self.localDevice.objectList: + indx = self.localDevice.objectList.index(object_identifier) + del self.localDevice.objectList[indx] # make sure the object knows it's detached from an application obj._app = None @@ -460,11 +470,17 @@ class Application(ApplicationServiceElement, Collector): # @bacpypes_debugging -class BIPSimpleApplication(Application): +class BIPSimpleApplication(Application, WhoIsIAmServices, ReadWritePropertyServices): 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) + Application.__init__(self, localDevice, deviceInfoCache, aseID) + + # local address might be useful for subclasses + if isinstance(localAddress, Address): + self.localAddress = localAddress + else: + self.localAddress = Address(localAddress) # include a application decoder self.asap = ApplicationServiceAccessPoint() @@ -504,11 +520,17 @@ class BIPSimpleApplication(Application): # @bacpypes_debugging -class BIPForeignApplication(Application): +class BIPForeignApplication(Application, WhoIsIAmServices, ReadWritePropertyServices): 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) + Application.__init__(self, localDevice, aseID) + + # local address might be useful for subclasses + if isinstance(localAddress, Address): + self.localAddress = localAddress + else: + self.localAddress = Address(localAddress) # include a application decoder self.asap = ApplicationServiceAccessPoint() diff --git a/py27/bacpypes/appservice.py b/py27/bacpypes/appservice.py index 77b6f0d..5006c0b 100755 --- a/py27/bacpypes/appservice.py +++ b/py27/bacpypes/appservice.py @@ -1061,8 +1061,7 @@ class StateMachineAccessPoint(Client, ServiceAccessPoint): Client.__init__(self, cid) ServiceAccessPoint.__init__(self, sap) - # save a reference to the local device object and the cache - self.localDevice = localDevice + # save a reference to the device information cache self.deviceInfoCache = deviceInfoCache # client settings diff --git a/py27/bacpypes/service/__init__.py b/py27/bacpypes/service/__init__.py index e07f560..0544dd6 100644 --- a/py27/bacpypes/service/__init__.py +++ b/py27/bacpypes/service/__init__.py @@ -7,4 +7,6 @@ Service Subpackage from . import test from . import device +from . import cov from . import file + diff --git a/py27/bacpypes/service/device.py b/py27/bacpypes/service/device.py old mode 100755 new mode 100644 index 5ad3cf6..4b1dbba --- a/py27/bacpypes/service/device.py +++ b/py27/bacpypes/service/device.py @@ -4,7 +4,17 @@ from ..debugging import bacpypes_debugging, ModuleLogger from ..capability import Capability from ..pdu import GlobalBroadcast -from ..apdu import WhoIsRequest, IAmRequest +from ..basetypes import ErrorType +from ..primitivedata import Atomic, Null, Unsigned +from ..constructeddata import Any, Array + +from ..apdu import Error, WhoIsRequest, IAmRequest, \ + SimpleAckPDU, ReadPropertyACK, ReadPropertyMultipleACK, \ + ReadAccessResult, ReadAccessResultElement, ReadAccessResultElementChoice +from ..errors import ExecutionError, InconsistentParameters, \ + MissingRequiredParameter, ParameterOutOfRange + +from ..object import PropertyError # some debugging _debug = 0 @@ -159,6 +169,8 @@ class WhoHasIHaveServices(Capability): def who_has(self, thing, address=None): if _debug: WhoHasIHaveServices._debug("who_has %r address=%r", thing, address) + raise NotImplementedError("i_have") + def do_WhoHasRequest(self, apdu): """Respond to a Who-Has request.""" if _debug: WhoHasIHaveServices._debug("do_WhoHasRequest, %r", apdu) @@ -168,19 +180,24 @@ class WhoHasIHaveServices(Capability): if _debug: WhoIsIAmServices._debug(" - no local device") return - key = (str(apdu.pduSource),) + # find the object if apdu.object.objectIdentifier is not None: - key += (str(apdu.object.objectIdentifier),) + obj = self.objectIdentifier.get(apdu.object.objectIdentifier, None) elif apdu.object.objectName is not None: - key += (apdu.object.objectName,) + obj = self.objectName.get(apdu.object.objectName, None) else: raise InconsistentParameters("object identifier or object name required") + if not obj: + raise Error(errorClass='object', errorCode='unknownObject') - ### check the objects for a match, call self.i_have(obj, address=apdu.pduSource) +# # send out the response +# self.i_have(obj, address=apdu.pduSource) def i_have(self, thing, address=None): if _debug: WhoHasIHaveServices._debug("i_have %r address=%r", thing, address) + raise NotImplementedError("i_have") + def do_IHaveRequest(self, apdu): """Respond to a I-Have request.""" if _debug: WhoHasIHaveServices._debug("do_IHaveRequest %r", apdu) @@ -203,109 +220,108 @@ class ReadWritePropertyServices(Capability): def do_ReadPropertyRequest(self, apdu): """Return the value of some property of one of our objects.""" - if _debug: Application._debug("do_ReadPropertyRequest %r", apdu) + if _debug: ReadWritePropertyServices._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") + if _debug: ReadWritePropertyServices._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 _debug: ReadWritePropertyServices._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) + raise Error(errorClass='object', errorCode='unknownObject') - # 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) + try: + # get the datatype + datatype = obj.get_datatype(apdu.propertyIdentifier) + if _debug: ReadWritePropertyServices._debug(" - datatype: %r", datatype) - # 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 {0} and got {1}" \ - .format(datatype.subtype.__name__, type(value).__name__)) - elif not isinstance(value, datatype): + # get the value + value = obj.ReadProperty(apdu.propertyIdentifier, apdu.propertyArrayIndex) + if _debug: ReadWritePropertyServices._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 {0} and got {1}" \ - .format(datatype.__name__, type(value).__name__)) - if _debug: Application._debug(" - encodeable value: %r", value) + .format(datatype.subtype.__name__, type(value).__name__)) + elif not isinstance(value, datatype): + raise TypeError("invalid result datatype, expecting {0} and got {1}" \ + .format(datatype.__name__, type(value).__name__)) + if _debug: ReadWritePropertyServices._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 + # 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) + # save the result in the property value + resp.propertyValue = Any() + resp.propertyValue.cast_in(value) + if _debug: ReadWritePropertyServices._debug(" - resp: %r", resp) - except PropertyError: - resp = Error(errorClass='object', errorCode='unknownProperty', context=apdu) - if _debug: Application._debug(" - resp: %r", resp) + except PropertyError: + raise Error(errorClass='object', errorCode='unknownProperty') # 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) + if _debug: ReadWritePropertyServices._debug("do_WritePropertyRequest %r", apdu) # get the object obj = self.get_object_id(apdu.objectIdentifier) - if _debug: Application._debug(" - object: %r", obj) - + if _debug: ReadWritePropertyServices._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) + raise Error(errorClass='object', errorCode='unknownObject') - # get the datatype, special case for null - if apdu.propertyValue.is_application_class_null(): - datatype = Null + 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: ReadWritePropertyServices._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: - datatype = obj.get_datatype(apdu.propertyIdentifier) - if _debug: Application._debug(" - datatype: %r", datatype) + value = apdu.propertyValue.cast_out(datatype.subtype) + else: + value = apdu.propertyValue.cast_out(datatype) + if _debug: ReadWritePropertyServices._debug(" - value: %r", value) - # 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) - # change the value - value = obj.WriteProperty(apdu.propertyIdentifier, value, apdu.propertyArrayIndex, apdu.priority) + # success + resp = SimpleAckPDU(context=apdu) + if _debug: ReadWritePropertyServices._debug(" - resp: %r", resp) - # success - resp = SimpleAckPDU(context=apdu) - - except PropertyError: - resp = Error(errorClass='object', errorCode='unknownProperty', context=apdu) - if _debug: Application._debug(" - resp: %r", resp) + except PropertyError: + raise Error(errorClass='object', errorCode='unknownProperty') # return the result self.response(resp) @@ -391,7 +407,7 @@ def read_property_to_result_element(obj, propertyIdentifier, propertyArrayIndex= return read_access_result_element # -# +# ReadWritePropertyMultipleServices # class ReadWritePropertyMultipleServices(Capability): diff --git a/py27/bacpypes/service/file.py b/py27/bacpypes/service/file.py old mode 100755 new mode 100644 index ecdad26..9a17035 --- a/py27/bacpypes/service/file.py +++ b/py27/bacpypes/service/file.py @@ -5,6 +5,12 @@ from ..capability import Capability from ..object import FileObject +from ..apdu import AtomicReadFileACK, AtomicReadFileACKAccessMethodChoice, \ + AtomicReadFileACKAccessMethodRecordAccess, \ + AtomicReadFileACKAccessMethodStreamAccess, \ + AtomicWriteFileACK +from ..errors import ExecutionError, MissingRequiredParameter + # some debugging _debug = 0 _log = ModuleLogger(globals()) @@ -112,15 +118,24 @@ class FileServices(Capability): if obj.fileAccessMethod != 'recordAccess': raise ExecutionError('services', 'invalidFileAccessMethod') + # simplify + record_access = apdu.accessMethod.recordAccess + + # check for required parameters + if record_access.fileStartRecord is None: + raise MissingRequiredParameter("fileStartRecord required") + if record_access.requestedRecordCount is None: + raise MissingRequiredParameter("requestedRecordCount required") + ### verify start is valid - double check this (empty files?) - if (apdu.accessMethod.recordAccess.fileStartRecord < 0) or \ - (apdu.accessMethod.recordAccess.fileStartRecord >= len(obj)): + if (record_access.fileStartRecord < 0) or \ + (record_access.fileStartRecord >= len(obj)): raise ExecutionError('services', 'invalidFileStartPosition') # pass along to the object end_of_file, record_data = obj.read_record( - apdu.accessMethod.recordAccess.fileStartRecord, - apdu.accessMethod.recordAccess.requestedRecordCount, + record_access.fileStartRecord, + record_access.requestedRecordCount, ) if _debug: FileServices._debug(" - record_data: %r", record_data) @@ -129,7 +144,7 @@ class FileServices(Capability): endOfFile=end_of_file, accessMethod=AtomicReadFileACKAccessMethodChoice( recordAccess=AtomicReadFileACKAccessMethodRecordAccess( - fileStartRecord=apdu.accessMethod.recordAccess.fileStartRecord, + fileStartRecord=record_access.fileStartRecord, returnedRecordCount=len(record_data), fileRecordData=record_data, ), @@ -141,15 +156,24 @@ class FileServices(Capability): if obj.fileAccessMethod != 'streamAccess': raise ExecutionError('services', 'invalidFileAccessMethod') + # simplify + stream_access = apdu.accessMethod.streamAccess + + # check for required parameters + if stream_access.fileStartPosition is None: + raise MissingRequiredParameter("fileStartPosition required") + if stream_access.requestedOctetCount is None: + raise MissingRequiredParameter("requestedOctetCount required") + ### verify start is valid - double check this (empty files?) - if (apdu.accessMethod.streamAccess.fileStartPosition < 0) or \ - (apdu.accessMethod.streamAccess.fileStartPosition >= len(obj)): + if (stream_access.fileStartPosition < 0) or \ + (stream_access.fileStartPosition >= len(obj)): raise ExecutionError('services', 'invalidFileStartPosition') # pass along to the object end_of_file, record_data = obj.read_stream( - apdu.accessMethod.streamAccess.fileStartPosition, - apdu.accessMethod.streamAccess.requestedOctetCount, + stream_access.fileStartPosition, + stream_access.requestedOctetCount, ) if _debug: FileServices._debug(" - record_data: %r", record_data) @@ -158,7 +182,7 @@ class FileServices(Capability): endOfFile=end_of_file, accessMethod=AtomicReadFileACKAccessMethodChoice( streamAccess=AtomicReadFileACKAccessMethodStreamAccess( - fileStartPosition=apdu.accessMethod.streamAccess.fileStartPosition, + fileStartPosition=stream_access.fileStartPosition, fileData=record_data, ), ), @@ -188,15 +212,26 @@ class FileServices(Capability): if obj.fileAccessMethod != 'recordAccess': raise ExecutionError('services', 'invalidFileAccessMethod') + # simplify + record_access = apdu.accessMethod.recordAccess + + # check for required parameters + if record_access.fileStartRecord is None: + raise MissingRequiredParameter("fileStartRecord required") + if record_access.recordCount is None: + raise MissingRequiredParameter("recordCount required") + if record_access.fileRecordData is None: + raise MissingRequiredParameter("fileRecordData required") + # check for read-only if obj.readOnly: raise ExecutionError('services', 'fileAccessDenied') # pass along to the object start_record = obj.write_record( - apdu.accessMethod.recordAccess.fileStartRecord, - apdu.accessMethod.recordAccess.recordCount, - apdu.accessMethod.recordAccess.fileRecordData, + record_access.fileStartRecord, + record_access.recordCount, + record_access.fileRecordData, ) if _debug: FileServices._debug(" - start_record: %r", start_record) @@ -210,14 +245,23 @@ class FileServices(Capability): if obj.fileAccessMethod != 'streamAccess': raise ExecutionError('services', 'invalidFileAccessMethod') + # simplify + stream_access = apdu.accessMethod.streamAccess + + # check for required parameters + if stream_access.fileStartPosition is None: + raise MissingRequiredParameter("fileStartPosition required") + if stream_access.fileData is None: + raise MissingRequiredParameter("fileData required") + # check for read-only if obj.readOnly: raise ExecutionError('services', 'fileAccessDenied') # pass along to the object start_position = obj.write_stream( - apdu.accessMethod.streamAccess.fileStartPosition, - apdu.accessMethod.streamAccess.fileData, + stream_access.fileStartPosition, + stream_access.fileData, ) if _debug: FileServices._debug(" - start_position: %r", start_position) diff --git a/py27/bacpypes/service/test.py b/py27/bacpypes/service/test.py index a1273c6..fb075dd 100644 --- a/py27/bacpypes/service/test.py +++ b/py27/bacpypes/service/test.py @@ -14,5 +14,5 @@ _log = ModuleLogger(globals()) def some_function(*args): if _debug: some_function._debug("f %r", args) - return x + 1 + return args[0] + 1