The issue that I'm having is that an external device makes a change to a Point and before the database has been updated, the BACnet stack has been updated by the process that checks the database, so the external change gets "lost" (its intermitent of course, since sometime the database is updated)
`# Function to create Binary Output Object
def create_binary_output(row):
try:
ArrayOfPropertyIdentifier = ArrayOf(PropertyIdentifier)
property_list = ArrayOfPropertyIdentifier([
'presentValue', 'statusFlags', 'eventState', 'inactiveText', 'activeText',
'outOfService', 'description', 'reliability', 'polarity'
])
# Convert the Present_Value to 'active' or 'inactive'
present_value = 'active' if row['Present_Value'] in [1, 1.0, 'active', True] else 'inactive'
# Initialize the BinaryOutputFeedbackObject
binary_output = BinaryOutputFeedbackObject(
objectName=row['Object_Name'],
objectIdentifier=(BinaryOutputObject.objectType, row['Object_Identifier']),
presentValue=present_value,
inactiveText=row['On_lable'],
activeText=row['Off_lable'],
statusFlags=calculate_status_flags(row),
eventState=row.get('Event_State', 0),
outOfService=bool(row['Out_Of_Service']),
polarity=row['Polarity'],
description=row['Description'],
reliability=row.get('Reliability', None),
propertyList=property_list,
)
# Initialize the priority array with default values
priority_array = PriorityArray([None] * 16)
# Set the Present_Value at the specified priority position if within range
if 'Priority' in row and 1 <= row['Priority'] <= 16:
priority_index = row['Priority'] # Adjust for 0-based index
priority_array[priority_index] = PriorityValue(characterString=CharacterString(present_value))
# Assign the priority array to the object
binary_output.priorityArray = priority_array
return binary_output
except Exception as e:
logging.error(f"{color.RED}An error occurred in create_binary_output: {e}{color.END}")
return None
@bacpypes_debugging
@register_object_type(vendor_id=47)
class BinaryOutputFeedbackObject(BinaryOutputCmdObject):
def init(self, *args, **kwargs):
super().init(*args, **kwargs)
# Initialize the objectIdentifier monitor
self._property_monitors.setdefault('objectIdentifier', []).append(self.objectIdentifier)
# listen for changes to the present value
self._property_monitors["presentValue"].append(self.check_feedback)
logging.info(f"{color.GREEN}BinaryOutputFeedbackObject initialized for {self.objectName}{color.END}")
def get_lowest_priority_value(self):
"""
Get the lowest non-null priority and its value from the priorityArray.
"""
for i, value in enumerate(self.priorityArray):
if value is not None:
# Handle different BACnet value types
if hasattr(value, 'real'):
value = value.real
elif hasattr(value, 'integer'):
value = value.integer
elif hasattr(value, 'unsigned'):
value = value.unsigned
elif hasattr(value, 'boolean'):
value = value.boolean
# logging.info(f"{color.YELLOW} Priority {i + 1} value is {value}{color.END}")
if value is not None:
return i + 1, value # BACnet priorities are 1-indexed
return 16, None
def check_feedback(self, old_value, new_value):
if new_value == old_value:
return
# Ensure the objectIdentifier is available
if 'objectIdentifier' not in self._property_monitors or not self._property_monitors['objectIdentifier']:
logging.error(f"{color.RED}BinaryOutputFeedbackObject: objectIdentifier not available in property monitors{color.END}")
return
object_identifier = self._property_monitors['objectIdentifier'][0]
object_identifier_value = object_identifier[1]
feedback_value = 1 if new_value == 'active' else 0
# Get the lowest non-null priority and its value
priority, priority_value = self.get_lowest_priority_value()
# Determine the value to update in the database
present_value_to_update = priority_value if priority != 16 else feedback_value
# Set Override_Value if priority is not 16
override_value = present_value_to_update if priority != 16 else None
# Set Override to 1 in the database if priority is 8
override_flag = 1 if priority == 8 else 0
logging.info(f"{color.GREEN}BinaryOutputFeedbackObject (ID: {object_identifier_value}, Name: {self.objectName}): Detected change from {old_value} to {new_value}{color.END}")
mydbProgram = None
mycursorProgram = None
try:
mydbProgram = poolProgram.get_connection()
mycursorProgram = mydbProgram.cursor(dictionary=True, buffered=True)
sql = (
"UPDATE Objects "
"SET Present_Value = %s, Updated_By = 'Bacnet Feedback', Priority = %s, Override_Value = %s, Override = %s "
"WHERE Object_Identifier = %s"
)
mycursorProgram.execute(sql, (present_value_to_update, priority, override_value, override_flag, object_identifier_value))
mydbProgram.commit()
logging.info(f"{color.GREEN}BinaryOutputFeedbackObject: Database updated for Object_Identifier {object_identifier_value} with Present_Value {present_value_to_update}, Priority {priority}, Override_Value {override_value}, Override {override_flag}{color.END}")
except (MySQLError, Exception) as e:
logging.error(f"{color.RED}BinaryOutputFeedbackObject: An error occurred while updating the database: {e}{color.END}")
finally:
if mycursorProgram:
mycursorProgram.close()
if mydbProgram:
mydbProgram.close()
`

(green text)
I've tried to implement a "debounce" feature, but that just complains that its unknown property
`from bacpypes.local.object import AnalogOutputCmdObject
import time
import logging
class AnalogOutputFeedbackObject(AnalogOutputCmdObject):
def init(self, *args, **kwargs):
self.last_update_time = 0
self.debounce_interval = 1 # seconds
if _debug:
AnalogOutputFeedbackObject._debug("init %r %r", args, kwargs)
super().init(*args, **kwargs)
# Initialize the objectIdentifier monitor
self._property_monitors.setdefault('objectIdentifier', [])
self._property_monitors['objectIdentifier'].append(self.objectIdentifier)
# listen for changes to the present value
self._property_monitors["presentValue"].append(self.check_feedback)
logging.info(f"{color.GREEN}AnalogOutputFeedbackObject initialized for {self.objectName}{color.END}")
def get_lowest_priority_value(self):
"""
Get the lowest non-null priority value from the priorityArray.
"""
for i, value in enumerate(self.priorityArray):
if value is not None:
# Handle different BACnet value types
if hasattr(value, 'real'):
value = value.real
elif hasattr(value, 'integer'):
value = value.integer
elif hasattr(value, 'unsigned'):
value = value.unsigned
elif hasattr(value, 'boolean'):
value = value.boolean
if value is not None:
return i + 1, value # BACnet priorities are 1-indexed
return 16, None
def check_feedback(self, old_value, new_value):
current_time = time.time()
if new_value == old_value:
return
if current_time - self.last_update_time < self.debounce_interval:
logging.info(f"{color.YELLOW}Debouncing... Skipping update for {self.objectName}{color.END}")
return
self.last_update_time = current_time
# Ensure the objectIdentifier is available
if 'objectIdentifier' not in self._property_monitors or not self._property_monitors['objectIdentifier']:
logging.error(f"{color.RED}AnalogOutputFeedbackObject: objectIdentifier not available in property monitors{color.END}")
return
object_identifier = self._property_monitors['objectIdentifier'][0]
object_identifier_value = object_identifier[1]
# Get the lowest non-null priority value
priority, priority_value = self.get_lowest_priority_value()
logging.info(f"{color.GREEN}AnalogOutputFeedbackObject (ID: {object_identifier_value}, Name: {self.objectName}): Detected change from {old_value} to {new_value} with priority {priority} and priority value {priority_value}{color.END}")
# Determine the value to update in the database
if priority != 16:
present_value_to_update = priority_value
else:
present_value_to_update = new_value
# Set Override_Value if priority is not 16
override_value = present_value_to_update if priority != 16 else None
# Set Override to 1 in the database if priority is 8
override_flag = 1 if priority == 8 else 0
mydbProgram = None
mycursorProgram = None
try:
mydbProgram = poolProgram.get_connection()
mycursorProgram = mydbProgram.cursor(dictionary=True, buffered=True)
sql = (
"UPDATE Objects "
"SET Present_Value = %s, Updated_By = 'Bacnet Feedback', Priority = %s, Override_Value = %s, Override = %s "
"WHERE Object_Identifier = %s"
)
mycursorProgram.execute(sql, (present_value_to_update, priority, override_value, override_flag, object_identifier_value))
mydbProgram.commit()
logging.info(f"{color.YELLOW}AnalogOutputFeedbackObject: Database updated for Object_Identifier {object_identifier_value} with Present_Value {present_value_to_update}, Priority {priority}, Override_Value {override_value}, Override {override_flag}{color.END}")
except (MySQLError, Exception) as e:
logging.error(f"{color.RED}AnalogOutputFeedbackObject: An error occurred while updating the database: {e}{color.END}")
finally:
if mycursorProgram:
mycursorProgram.close()
if mydbProgram:
mydbProgram.close()
`
The issue that I'm having is that an external device makes a change to a Point and before the database has been updated, the BACnet stack has been updated by the process that checks the database, so the external change gets "lost" (its intermitent of course, since sometime the database is updated)
`# Function to create Binary Output Object
def create_binary_output(row):
try:
ArrayOfPropertyIdentifier = ArrayOf(PropertyIdentifier)
property_list = ArrayOfPropertyIdentifier([
'presentValue', 'statusFlags', 'eventState', 'inactiveText', 'activeText',
'outOfService', 'description', 'reliability', 'polarity'
])
@bacpypes_debugging
@register_object_type(vendor_id=47)
class BinaryOutputFeedbackObject(BinaryOutputCmdObject):
def init(self, *args, **kwargs):
super().init(*args, **kwargs)
`

(green text)
I've tried to implement a "debounce" feature, but that just complains that its unknown property
`from bacpypes.local.object import AnalogOutputCmdObject
import time
import logging
class AnalogOutputFeedbackObject(AnalogOutputCmdObject):
def init(self, *args, **kwargs):
self.last_update_time = 0
self.debounce_interval = 1 # seconds
if _debug:
AnalogOutputFeedbackObject._debug("init %r %r", args, kwargs)
super().init(*args, **kwargs)
`