from functools import wraps
from bacpypes.basetypes import EngineeringUnits
from bacpypes.local.object import Commandable
from bacpypes.object import (
Property,
TrendLogObject,
register_object_type,
)
from bacpypes.primitivedata import CharacterString
_SHOULD_BE_COMMANDABLE = ["relinquishDefault", "outOfService", "lowLimit", "highLimit"]
"""
Template
Decorators is an effort to handle object creation without explicitly declare a new class
depending on the properties or features required.
# Usage
## bacnet_properties
This decorator takes a dict as argument defining supplmental properties
## bacnet_property
This decorator takes a simple property and its default value, adds it to object
## Commandable
This decoratore will modify the base class and create a new class that inherit from _commando (see local.object.py)
## Add feature
This decorator works the same than commandable. Could serve as a way to add behaviour like MinOnOff, events, limits, etc...
## Example::
properties = {"outOfService" : False,
"relinquishDefault" : 0,
"units": "degreesCelsius",
"highLimit": 98}
@bacnet_properties(properties)
@commandable()
def av(instance, objectName, presentValue, description):
OBJECT_TYPE = AnalogValueObject
return create(OBJECT_TYPE,instance, objectName, presentValue, description)
@add_feature(MinOnOff)
@commandable()
def bv(instance, objectName, presentValue, description):
OBJECT_TYPE = BinaryValueObject
return create(OBJECT_TYPE,instance, objectName, presentValue, description)
@commandable()
def datepattern(instance, objectName, presentValue, description):
OBJECT_TYPE = DatePatternValueObject
return create(OBJECT_TYPE,instance, objectName, presentValue, description)
### The creation takes place when the functions are called
a = av(1,'AnalogValueName',10,'AnalogValue Description')
b = bv(1,'BV Name','inactive','BinaryValue Description')
c = datepattern(1,'My Date Pattern',None,'DatePattern Description')
"""
def _allowed_prop(obj):
allowed_prop = {}
for each in type(obj).properties:
allowed_prop[each.identifier] = each.datatype
for base in type(obj).__bases__:
try:
for each in base.properties:
allowed_prop[each.identifier] = each.datatype
except AttributeError:
pass
return allowed_prop
def _mutable(property_name, force_mutable=False):
if property_name in _SHOULD_BE_COMMANDABLE and not force_mutable:
mutable = True
elif force_mutable:
mutable = force_mutable
else:
mutable = False
return mutable
[docs]def make_commandable():
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if callable(func):
obj = func(*args, **kwargs)
else:
obj = func
allowed_prop = _allowed_prop(obj)
_type = allowed_prop["presentValue"]
_commando = Commandable(_type)
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__ + "Cmd"
new_type = type(base_cls_name, (_commando, base_cls), {})
new_type.__name__ = base_cls_name
register_object_type(new_type, vendor_id=842)
objectType, instance, objectName, presentValue, description = args
new_object = new_type(
objectIdentifier=(base_cls.objectType, instance),
objectName="{}".format(objectName),
presentValue=presentValue,
description=CharacterString("{}".format(description)),
)
return new_object
return wrapper
return decorate
[docs]def add_feature(cls):
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if callable(func):
obj = func(*args, **kwargs)
else:
obj = func
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__ + cls.__name__
new_type = type(base_cls_name, (cls, base_cls), {})
register_object_type(new_type, vendor_id=842)
instance, objectName, presentValue, description = args
new_object = new_type(
objectIdentifier=(base_cls.objectType, instance),
objectName="{}".format(objectName),
presentValue=presentValue,
description=CharacterString("{}".format(description)),
)
return new_object
return wrapper
return decorate
[docs]def bacnet_property(property_name, value, *, force_mutable=None):
"""
Given a property, add it to the object
"""
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if callable(func):
obj = func(*args, **kwargs)
else:
obj = func
allowed_prop = _allowed_prop(obj)
mutable = _mutable(property_name)
if property_name == "units":
new_prop = EngineeringUnits.enumerations[value]
obj.units = new_prop
else:
try:
new_prop = Property(
property_name,
allowed_prop[property_name],
default=value,
mutable=mutable,
)
except KeyError:
raise ValueError(
"Invalid property ({}) for object".format(property_name)
)
obj.add_property(new_prop)
return obj
return wrapper
return decorate
[docs]def bacnet_properties(properties):
"""
Given a dict of properties, add them to the object
"""
def decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if callable(func):
obj = func(*args, **kwargs)
else:
obj = func
allowed_prop = _allowed_prop(obj)
for property_name, value in properties.items():
if property_name == "units":
new_prop = EngineeringUnits.enumerations[value]
obj.units = new_prop
else:
try:
mutable = _mutable(property_name)
new_prop = Property(
property_name,
allowed_prop[property_name],
default=value,
mutable=mutable,
)
except KeyError:
raise ValueError(
"Invalid property ({}) for object".format(property_name)
)
obj.add_property(new_prop)
return obj
return wrapper
return decorate
[docs]def create(object_type, instance, objectName, value, description):
if object_type is TrendLogObject:
new_object = object_type(
objectIdentifier=(object_type.objectType, instance),
objectName="{}".format(objectName),
logBuffer=value,
description=CharacterString("{}".format(description)),
)
else:
new_object = object_type(
objectIdentifier=(object_type.objectType, instance),
objectName="{}".format(objectName),
presentValue=value,
description=CharacterString("{}".format(description)),
)
return new_object