mirror of
https://github.com/JoelBender/bacpypes
synced 2025-09-28 22:15:23 +08:00
426 lines
14 KiB
Python
426 lines
14 KiB
Python
#!/usr/bin/env python
|
|
|
|
"""
|
|
Rebuilt Commandable
|
|
"""
|
|
|
|
from bacpypes.debugging import bacpypes_debugging, ModuleLogger
|
|
from bacpypes.consolelogging import ConfigArgumentParser
|
|
|
|
from bacpypes.core import run
|
|
from bacpypes.task import OneShotTask
|
|
from bacpypes.errors import ExecutionError
|
|
|
|
from bacpypes.primitivedata import BitString, CharacterString, Date, Integer, \
|
|
Double, Enumerated, OctetString, Real, Time, Unsigned
|
|
from bacpypes.basetypes import BinaryPV, ChannelValue, DateTime, DoorValue, PriorityValue, \
|
|
PriorityArray
|
|
from bacpypes.object import Property, ReadableProperty, WritableProperty, \
|
|
register_object_type, \
|
|
AccessDoorObject, AnalogOutputObject, AnalogValueObject, \
|
|
BinaryOutputObject, BinaryValueObject, BitStringValueObject, CharacterStringValueObject, \
|
|
DateValueObject, DatePatternValueObject, DateTimePatternValueObject, \
|
|
DateTimeValueObject, IntegerValueObject, \
|
|
LargeAnalogValueObject, LightingOutputObject, MultiStateOutputObject, \
|
|
MultiStateValueObject, OctetStringValueObject, PositiveIntegerValueObject, \
|
|
TimeValueObject, TimePatternValueObject, ChannelObject
|
|
|
|
from bacpypes.app import BIPSimpleApplication
|
|
from bacpypes.local.object import CurrentPropertyListMixIn
|
|
from bacpypes.local.device import LocalDeviceObject
|
|
|
|
# some debugging
|
|
_debug = 0
|
|
_log = ModuleLogger(globals())
|
|
|
|
|
|
#
|
|
# Commandable
|
|
#
|
|
|
|
@bacpypes_debugging
|
|
def Commandable(datatype, presentValue='presentValue', priorityArray='priorityArray', relinquishDefault='relinquishDefault'):
|
|
if _debug: Commandable._debug("Commandable %r ...", datatype)
|
|
|
|
class _Commando(object):
|
|
|
|
properties = [
|
|
WritableProperty(presentValue, datatype),
|
|
ReadableProperty(priorityArray, PriorityArray),
|
|
ReadableProperty(relinquishDefault, datatype),
|
|
]
|
|
|
|
_pv_choice = None
|
|
|
|
def __init__(self, **kwargs):
|
|
super(_Commando, self).__init__(**kwargs)
|
|
|
|
# build a default value in case one is needed
|
|
default_value = datatype().value
|
|
if issubclass(datatype, Enumerated):
|
|
default_value = datatype._xlate_table[default_value]
|
|
if _debug: Commandable._debug(" - default_value: %r", default_value)
|
|
|
|
# see if a present value was provided
|
|
if (presentValue not in kwargs):
|
|
setattr(self, presentValue, default_value)
|
|
|
|
# see if a priority array was provided
|
|
if (priorityArray not in kwargs):
|
|
setattr(self, priorityArray, PriorityArray())
|
|
|
|
# see if a present value was provided
|
|
if (relinquishDefault not in kwargs):
|
|
setattr(self, relinquishDefault, default_value)
|
|
|
|
def _highest_priority_value(self):
|
|
if _debug: Commandable._debug("_highest_priority_value")
|
|
|
|
priority_array = getattr(self, priorityArray)
|
|
for i in range(1, 17):
|
|
priority_value = priority_array[i]
|
|
if priority_value.null is None:
|
|
if _debug: Commandable._debug(" - found at index: %r", i)
|
|
|
|
value = getattr(priority_value, _Commando._pv_choice)
|
|
value_source = "###"
|
|
|
|
if issubclass(datatype, Enumerated):
|
|
value = datatype._xlate_table[value]
|
|
if _debug: Commandable._debug(" - remapped enumeration: %r", value)
|
|
|
|
break
|
|
else:
|
|
value = getattr(self, relinquishDefault)
|
|
value_source = None
|
|
|
|
if _debug: Commandable._debug(" - value, value_source: %r, %r", value, value_source)
|
|
|
|
# return what you found
|
|
return value, value_source
|
|
|
|
def WriteProperty(self, property, value, arrayIndex=None, priority=None, direct=False):
|
|
if _debug: Commandable._debug("WriteProperty %r %r arrayIndex=%r priority=%r direct=%r", property, value, arrayIndex, priority, direct)
|
|
|
|
# when writing to the presentValue with a priority
|
|
if (property == presentValue):
|
|
if _debug: Commandable._debug(" - writing to %s, priority %r", presentValue, priority)
|
|
|
|
# default (lowest) priority
|
|
if priority is None:
|
|
priority = 16
|
|
if _debug: Commandable._debug(" - translate to priority array, index %d", priority)
|
|
|
|
# translate to updating the priority array
|
|
property = priorityArray
|
|
arrayIndex = priority
|
|
priority = None
|
|
|
|
# update the priority array entry
|
|
if (property == priorityArray):
|
|
if (arrayIndex is None):
|
|
if _debug: Commandable._debug(" - writing entire %s", priorityArray)
|
|
|
|
# pass along the request
|
|
super(_Commando, self).WriteProperty(
|
|
property, value,
|
|
arrayIndex=arrayIndex, priority=priority, direct=direct,
|
|
)
|
|
else:
|
|
if _debug: Commandable._debug(" - writing to %s, array index %d", priorityArray, arrayIndex)
|
|
|
|
# check the bounds
|
|
if arrayIndex == 0:
|
|
raise ExecutionError(errorClass='property', errorCode='writeAccessDenied')
|
|
if (arrayIndex < 1) or (arrayIndex > 16):
|
|
raise ExecutionError(errorClass='property', errorCode='invalidArrayIndex')
|
|
|
|
# update the specific priorty value element
|
|
priority_value = getattr(self, priorityArray)[arrayIndex]
|
|
if _debug: Commandable._debug(" - priority_value: %r", priority_value)
|
|
|
|
# the null or the choice has to be set, the other clear
|
|
if value is ():
|
|
if _debug: Commandable._debug(" - write a null")
|
|
priority_value.null = value
|
|
setattr(priority_value, _Commando._pv_choice, None)
|
|
else:
|
|
if _debug: Commandable._debug(" - write a value")
|
|
|
|
if issubclass(datatype, Enumerated):
|
|
value = datatype._xlate_table[value]
|
|
if _debug: Commandable._debug(" - remapped enumeration: %r", value)
|
|
|
|
priority_value.null = None
|
|
setattr(priority_value, _Commando._pv_choice, value)
|
|
|
|
# look for the highest priority value
|
|
value, value_source = self._highest_priority_value()
|
|
|
|
# compare with the current value
|
|
current_value = getattr(self, presentValue)
|
|
if value == current_value:
|
|
if _debug: Commandable._debug(" - no present value change")
|
|
return
|
|
|
|
# turn this into a present value change
|
|
property = presentValue
|
|
arrayIndex = priority = None
|
|
|
|
# allow the request to pass through
|
|
if _debug: Commandable._debug(" - super: %r %r arrayIndex=%r priority=%r", property, value, arrayIndex, priority)
|
|
|
|
super(_Commando, self).WriteProperty(
|
|
property, value,
|
|
arrayIndex=arrayIndex, priority=priority, direct=direct,
|
|
)
|
|
|
|
# look up a matching priority value choice
|
|
for element in PriorityValue.choiceElements:
|
|
if issubclass(datatype, element.klass):
|
|
_Commando._pv_choice = element.name
|
|
break
|
|
else:
|
|
_Commando._pv_choice = 'constructedValue'
|
|
if _debug: Commandable._debug(" - _pv_choice: %r", _Commando._pv_choice)
|
|
|
|
# return the class
|
|
return _Commando
|
|
|
|
#
|
|
# MinOnOffTask
|
|
#
|
|
|
|
@bacpypes_debugging
|
|
class MinOnOffTask(OneShotTask):
|
|
|
|
def __init__(self, binary_obj):
|
|
if _debug: MinOnOffTask._debug("__init__ %s", repr(binary_obj))
|
|
OneShotTask.__init__(self)
|
|
|
|
# save a reference to the object
|
|
self.binary_obj = binary_obj
|
|
|
|
# listen for changes to the present value
|
|
self.binary_obj._property_monitors['presentValue'].append(self.present_value_change)
|
|
|
|
def present_value_change(self, old_value, new_value):
|
|
if _debug: MinOnOffTask._debug("present_value_change %r %r", old_value, new_value)
|
|
|
|
# if there's no value change, skip all this
|
|
if old_value == new_value:
|
|
if _debug: MinOnOffTask._debug(" - no state change")
|
|
return
|
|
|
|
# get the minimum on/off time
|
|
if new_value == 'inactive':
|
|
task_delay = getattr(self.binary_obj, 'minimumOnTime') or 0
|
|
if _debug: MinOnOffTask._debug(" - minimum on: %r", task_delay)
|
|
elif new_value == 'active':
|
|
task_delay = getattr(self.binary_obj, 'minimumOffTime') or 0
|
|
if _debug: MinOnOffTask._debug(" - minimum off: %r", task_delay)
|
|
else:
|
|
raise ValueError("unrecognized present value for %r: %r" % (self.binary_obj.objectIdentifier, new_value))
|
|
|
|
# if there's no delay, don't bother
|
|
if not task_delay:
|
|
if _debug: MinOnOffTask._debug(" - no delay")
|
|
return
|
|
|
|
# set the value at priority 6
|
|
self.binary_obj.WriteProperty('presentValue', new_value, priority=6)
|
|
|
|
# install this to run, if there is a delay
|
|
self.install_task(delta=task_delay)
|
|
|
|
def process_task(self):
|
|
if _debug: MinOnOffTask._debug("process_task(%s)", self.binary_obj.objectName)
|
|
|
|
# clear the value at priority 6
|
|
self.binary_obj.WriteProperty('presentValue', (), priority=6)
|
|
|
|
#
|
|
# MinOnOff
|
|
#
|
|
|
|
@bacpypes_debugging
|
|
class MinOnOff(object):
|
|
|
|
def __init__(self, **kwargs):
|
|
if _debug: MinOnOff._debug("__init__ ...")
|
|
super(MinOnOff, self).__init__(**kwargs)
|
|
|
|
# create the timer task
|
|
self._min_on_off_task = MinOnOffTask(self)
|
|
|
|
#
|
|
# Commandable Standard Objects
|
|
#
|
|
|
|
class AccessDoorObjectCmd(Commandable(DoorValue), AccessDoorObject):
|
|
pass
|
|
|
|
class AnalogOutputObjectCmd(Commandable(Real), AnalogOutputObject):
|
|
pass
|
|
|
|
class AnalogValueObjectCmd(Commandable(Real), AnalogValueObject):
|
|
pass
|
|
|
|
### class BinaryLightingOutputObjectCmd(Commandable(Real), BinaryLightingOutputObject):
|
|
### pass
|
|
|
|
class BinaryOutputObjectCmd(Commandable(BinaryPV), MinOnOff, BinaryOutputObject):
|
|
pass
|
|
|
|
class BinaryValueObjectCmd(Commandable(BinaryPV), MinOnOff, BinaryValueObject):
|
|
pass
|
|
|
|
class BitStringValueObjectCmd(Commandable(BitString), BitStringValueObject):
|
|
pass
|
|
|
|
class CharacterStringValueObjectCmd(Commandable(CharacterString), CharacterStringValueObject):
|
|
pass
|
|
|
|
class DateValueObjectCmd(Commandable(Date), DateValueObject):
|
|
pass
|
|
|
|
class DatePatternValueObjectCmd(Commandable(Date), DatePatternValueObject):
|
|
pass
|
|
|
|
class DateTimeValueObjectCmd(Commandable(DateTime), DateTimeValueObject):
|
|
pass
|
|
|
|
class DateTimePatternValueObjectCmd(Commandable(DateTime), DateTimePatternValueObject):
|
|
pass
|
|
|
|
class IntegerValueObjectCmd(Commandable(Integer), IntegerValueObject):
|
|
pass
|
|
|
|
class LargeAnalogValueObjectCmd(Commandable(Double), LargeAnalogValueObject):
|
|
pass
|
|
|
|
class LightingOutputObjectCmd(Commandable(Real), LightingOutputObject):
|
|
pass
|
|
|
|
class MultiStateOutputObjectCmd(Commandable(Unsigned), MultiStateOutputObject):
|
|
pass
|
|
|
|
class MultiStateValueObjectCmd(Commandable(Unsigned), MultiStateValueObject):
|
|
pass
|
|
|
|
class OctetStringValueObjectCmd(Commandable(OctetString), OctetStringValueObject):
|
|
pass
|
|
|
|
class PositiveIntegerValueObjectCmd(Commandable(Unsigned), PositiveIntegerValueObject):
|
|
pass
|
|
|
|
class TimeValueObjectCmd(Commandable(Time), TimeValueObject):
|
|
pass
|
|
|
|
class TimePatternValueObjectCmd(Commandable(Time), TimePatternValueObject):
|
|
pass
|
|
|
|
#
|
|
# ChannelValueProperty
|
|
#
|
|
|
|
class ChannelValueProperty(Property):
|
|
|
|
def __init__(self):
|
|
if _debug: ChannelValueProperty._debug("__init__")
|
|
Property.__init__(self, 'presentValue', ChannelValue, default=None, optional=False, mutable=True)
|
|
|
|
def WriteProperty(self, obj, value, arrayIndex=None, priority=None, direct=False):
|
|
if _debug: ChannelValueProperty._debug("WriteProperty %r %r arrayIndex=%r priority=%r direct=%r", obj, value, arrayIndex, priority, direct)
|
|
|
|
### Clause 12.53.5, page 487
|
|
raise NotImplementedError()
|
|
|
|
#
|
|
# ChannelObjectCmd
|
|
#
|
|
|
|
class ChannelObjectCmd(ChannelObject):
|
|
|
|
properties = [
|
|
ChannelValueProperty(),
|
|
]
|
|
|
|
##
|
|
##
|
|
##
|
|
##
|
|
##
|
|
|
|
@register_object_type(vendor_id=999)
|
|
class LocalAnalogValueObjectCmd(CurrentPropertyListMixIn, AnalogValueObjectCmd):
|
|
pass
|
|
|
|
@register_object_type(vendor_id=999)
|
|
class LocalBinaryOutputObjectCmd(CurrentPropertyListMixIn, BinaryOutputObjectCmd):
|
|
pass
|
|
|
|
@register_object_type(vendor_id=999)
|
|
class LocalDateValueObjectCmd(CurrentPropertyListMixIn, DateValueObjectCmd):
|
|
pass
|
|
|
|
#
|
|
# __main__
|
|
#
|
|
|
|
def main():
|
|
# parse the command line arguments
|
|
args = ConfigArgumentParser(description=__doc__).parse_args()
|
|
|
|
if _debug: _log.debug("initialization")
|
|
if _debug: _log.debug(" - args: %r", args)
|
|
|
|
# make a device object
|
|
this_device = LocalDeviceObject(ini=args.ini)
|
|
if _debug: _log.debug(" - this_device: %r", this_device)
|
|
|
|
# make a sample application
|
|
this_application = BIPSimpleApplication(this_device, args.ini.address)
|
|
|
|
# make a commandable analog value object, add to the device
|
|
avo1 = LocalAnalogValueObjectCmd(
|
|
objectIdentifier=('analogValue', 1),
|
|
objectName='avo1',
|
|
)
|
|
if _debug: _log.debug(" - avo1: %r", avo1)
|
|
this_application.add_object(avo1)
|
|
|
|
# make a commandable binary output object, add to the device
|
|
boo1 = LocalBinaryOutputObjectCmd(
|
|
objectIdentifier=('binaryOutput', 1),
|
|
objectName='boo1',
|
|
presentValue='inactive',
|
|
relinquishDefault='inactive',
|
|
minimumOnTime=5, # let it warm up
|
|
minimumOffTime=10, # let it cool off
|
|
)
|
|
if _debug: _log.debug(" - boo1: %r", boo1)
|
|
this_application.add_object(boo1)
|
|
|
|
# get the current date
|
|
today = Date().now()
|
|
|
|
# make a commandable date value object, add to the device
|
|
dvo1 = LocalDateValueObjectCmd(
|
|
objectIdentifier=('dateValue', 1),
|
|
objectName='dvo1',
|
|
presentValue=today.value,
|
|
)
|
|
if _debug: _log.debug(" - dvo1: %r", dvo1)
|
|
this_application.add_object(dvo1)
|
|
|
|
if _debug: _log.debug("running")
|
|
|
|
run()
|
|
|
|
_log.debug("fini")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|