Source code for BAC0.core.devices.local.decorator

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