Skip to content

Commit

Permalink
merging in #144 and cleaning up
Browse files Browse the repository at this point in the history
  • Loading branch information
JoelBender committed Apr 7, 2018
2 parents 5add964 + 80f3fa5 commit 26bd59c
Show file tree
Hide file tree
Showing 91 changed files with 5,236 additions and 948 deletions.
2 changes: 2 additions & 0 deletions py25/bacpypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@

from . import app
from . import appservice

from . import local
from . import service

#
Expand Down
38 changes: 36 additions & 2 deletions py25/bacpypes/constructeddata.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def decode(self, taglist):

for element in self.sequenceElements:
tag = taglist.Peek()
if _debug: Sequence._debug(" - element, tag: %r, %r", element, tag)

# no more elements
if tag is None:
Expand Down Expand Up @@ -190,7 +191,29 @@ def decode(self, taglist):
if tag.tagClass != Tag.closingTagClass or tag.tagNumber != element.context:
raise InvalidTag("%s expected closing tag %d" % (element.name, element.context))

# check for an atomic element
# check for an any atomic element
elif issubclass(element.klass, AnyAtomic):
# convert it to application encoding
if element.context is not None:
raise InvalidTag("%s any atomic with context tag %d" % (element.name, element.context))

if tag.tagClass != Tag.applicationTagClass:
if not element.optional:
raise InvalidParameterDatatype("%s expected any atomic application tag" % (element.name,))
else:
setattr(self, element.name, None)
continue

# consume the tag
taglist.Pop()

# a helper cooperates between the atomic value and the tag
helper = element.klass(tag)

# now save the value
setattr(self, element.name, helper.value)

# check for specific kind of atomic element, or the context says what kind
elif issubclass(element.klass, Atomic):
# convert it to application encoding
if element.context is not None:
Expand Down Expand Up @@ -409,6 +432,9 @@ def __len__(self):
def __getitem__(self, item):
return self.value[item]

def __iter__(self):
return iter(self.value)

def encode(self, taglist):
if _debug: _SequenceOf._debug("(%r)encode %r", self.__class__.__name__, taglist)
for value in self.value:
Expand Down Expand Up @@ -753,6 +779,9 @@ def __delitem__(self, item):
del self.value[item]
self.value[0] -= 1

def __iter__(self):
return iter(self.value[1:])

def index(self, value):
# only search through values
for i in range(1, self.value[0] + 1):
Expand Down Expand Up @@ -1349,8 +1378,13 @@ def decode(self, tag):
# get the data
self.value = tag.app_to_object()

@classmethod
def is_valid(cls, arg):
"""Return True if arg is valid value for the class."""
return isinstance(arg, Atomic) and not isinstance(arg, AnyAtomic)

def __str__(self):
return "AnyAtomic(%s)" % (str(self.value), )
return "%s(%s)" % (self.__class__.__name__, str(self.value))

def __repr__(self):
desc = self.__module__ + '.' + self.__class__.__name__
Expand Down
11 changes: 11 additions & 0 deletions py25/bacpypes/local/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env python

"""
Local Object Subpackage
"""

from . import object
from . import device
from . import file
from . import schedule

159 changes: 159 additions & 0 deletions py25/bacpypes/local/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python

from ..debugging import bacpypes_debugging, ModuleLogger

from ..primitivedata import Date, Time, ObjectIdentifier
from ..constructeddata import ArrayOf
from ..basetypes import ServicesSupported

from ..errors import ExecutionError
from ..object import register_object_type, registered_object_types, \
Property, DeviceObject

from .object import CurrentPropertyListMixIn

# some debugging
_debug = 0
_log = ModuleLogger(globals())

#
# CurrentLocalDate
#

class CurrentLocalDate(Property):

def __init__(self):
Property.__init__(self, 'localDate', Date, default=(), optional=True, mutable=False)

def ReadProperty(self, obj, arrayIndex=None):
if arrayIndex is not None:
raise ExecutionError(errorClass='property', errorCode='propertyIsNotAnArray')

# get the value
now = Date()
now.now()
return now.value

def WriteProperty(self, obj, value, arrayIndex=None, priority=None, direct=False):
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')

#
# CurrentLocalTime
#

class CurrentLocalTime(Property):

def __init__(self):
Property.__init__(self, 'localTime', Time, default=(), optional=True, mutable=False)

def ReadProperty(self, obj, arrayIndex=None):
if arrayIndex is not None:
raise ExecutionError(errorClass='property', errorCode='propertyIsNotAnArray')

# get the value
now = Time()
now.now()
return now.value

def WriteProperty(self, obj, value, arrayIndex=None, priority=None, direct=False):
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')

#
# CurrentProtocolServicesSupported
#

class CurrentProtocolServicesSupported(Property):

def __init__(self):
if _debug: CurrentProtocolServicesSupported._debug("__init__")
Property.__init__(self, 'protocolServicesSupported', ServicesSupported, default=None, optional=True, mutable=False)

def ReadProperty(self, obj, arrayIndex=None):
if _debug: CurrentProtocolServicesSupported._debug("ReadProperty %r %r", obj, arrayIndex)

# not an array
if arrayIndex is not None:
raise ExecutionError(errorClass='property', errorCode='propertyIsNotAnArray')

# return what the application says
return obj._app.get_services_supported()

def WriteProperty(self, obj, value, arrayIndex=None, priority=None, direct=False):
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')

bacpypes_debugging(CurrentProtocolServicesSupported)

#
# LocalDeviceObject
#

class LocalDeviceObject(CurrentPropertyListMixIn, DeviceObject):

properties = [
CurrentLocalTime(),
CurrentLocalDate(),
CurrentProtocolServicesSupported(),
]

defaultProperties = \
{ 'maxApduLengthAccepted': 1024
, 'segmentationSupported': 'segmentedBoth'
, 'maxSegmentsAccepted': 16
, 'apduSegmentTimeout': 5000
, '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

for key, value in kwargs.items():
if key.startswith("_"):
setattr(self, key, value)
del kwargs[key]

# 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 properties this class implements
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")
if 'protocolServicesSupported' in kwargs:
raise RuntimeError("protocolServicesSupported is provided by LocalDeviceObject and cannot be overridden")

# the object identifier is required for the object list
if 'objectIdentifier' not in kwargs:
raise RuntimeError("objectIdentifier is required")

# coerce the object identifier
object_identifier = kwargs['objectIdentifier']
if isinstance(object_identifier, (int, long)):
object_identifier = ('device', object_identifier)

# the object list is provided
if 'objectList' in kwargs:
raise RuntimeError("objectList is provided by LocalDeviceObject and cannot be overridden")
kwargs['objectList'] = ArrayOf(ObjectIdentifier)([object_identifier])

# check for a minimum value
if kwargs['maxApduLengthAccepted'] < 50:
raise ValueError("invalid max APDU length accepted")

# dump the updated attributes
if _debug: LocalDeviceObject._debug(" - updated kwargs: %r", kwargs)

# proceed as usual
super(LocalDeviceObject, self).__init__(**kwargs)

bacpypes_debugging(LocalDeviceObject)

91 changes: 91 additions & 0 deletions py25/bacpypes/local/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python

from ..debugging import bacpypes_debugging, ModuleLogger
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())

#
# Local Record Access File Object Type
#

class LocalRecordAccessFileObject(FileObject):

def __init__(self, **kwargs):
""" Initialize a record accessed file object. """
if _debug:
LocalRecordAccessFileObject._debug("__init__ %r",
kwargs,
)

# verify the file access method or provide it
if 'fileAccessMethod' in kwargs:
if kwargs['fileAccessMethod'] != 'recordAccess':
raise ValueError("inconsistent file access method")
else:
kwargs['fileAccessMethod'] = 'recordAccess'

# continue with initialization
FileObject.__init__(self, **kwargs)

def __len__(self):
""" Return the number of records. """
raise NotImplementedError("__len__")

def read_record(self, start_record, record_count):
""" Read a number of records starting at a specific record. """
raise NotImplementedError("read_record")

def write_record(self, start_record, record_count, record_data):
""" Write a number of records, starting at a specific record. """
raise NotImplementedError("write_record")

bacpypes_debugging(LocalRecordAccessFileObject)

#
# Local Stream Access File Object Type
#

class LocalStreamAccessFileObject(FileObject):

def __init__(self, **kwargs):
""" Initialize a stream accessed file object. """
if _debug:
LocalStreamAccessFileObject._debug("__init__ %r",
kwargs,
)

# verify the file access method or provide it
if 'fileAccessMethod' in kwargs:
if kwargs['fileAccessMethod'] != 'streamAccess':
raise ValueError("inconsistent file access method")
else:
kwargs['fileAccessMethod'] = 'streamAccess'

# continue with initialization
FileObject.__init__(self, **kwargs)

def __len__(self):
""" Return the number of octets in the file. """
raise NotImplementedError("write_file")

def read_stream(self, start_position, octet_count):
""" Read a chunk of data out of the file. """
raise NotImplementedError("read_stream")

def write_stream(self, start_position, data):
""" Write a number of octets, starting at a specific offset. """
raise NotImplementedError("write_stream")

bacpypes_debugging(LocalStreamAccessFileObject)

Loading

0 comments on commit 26bd59c

Please sign in to comment.