/**
 * @file
 * @brief Base "class" for handling all BACnet objects belonging
 * to a BACnet device, as well as Device-specific properties.
 * @author Steve Karg <skarg@users.sourceforge.net>
 * @date March 2024
 * @copyright SPDX-License-Identifier: MIT
 */
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacapp.h"
#include "bacnet/datetime.h"
#include "bacnet/apdu.h"
#include "bacnet/wp.h" /* WriteProperty handling */
#include "bacnet/rp.h" /* ReadProperty handling */
#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */
#include "bacnet/version.h"
#if defined(BACDL_MSTP)
#include "bacnet/datalink/dlmstp.h"
#endif
#include "bacnet/basic/services.h"
#include "bacnet/basic/binding/address.h"
/* include the device object */
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/object/acc.h"
#include "bacnet/basic/object/ai.h"
#include "bacnet/basic/object/ao.h"
#include "bacnet/basic/object/av.h"
#include "bacnet/basic/object/bi.h"
#include "bacnet/basic/object/bo.h"
#include "bacnet/basic/object/bv.h"
#include "bacnet/basic/object/calendar.h"
#include "bacnet/basic/object/command.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/object/lc.h"
#include "bacnet/basic/object/lsp.h"
#include "bacnet/basic/object/lsz.h"
#include "bacnet/basic/object/ms-input.h"
#include "bacnet/basic/object/mso.h"
#include "bacnet/basic/object/msv.h"
#include "bacnet/basic/object/schedule.h"
#include "bacnet/basic/object/structured_view.h"
#include "bacnet/basic/object/trendlog.h"
#include "bacnet/basic/object/nc.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/object/bitstring_value.h"
#include "bacnet/basic/object/csv.h"
#include "bacnet/basic/object/iv.h"
#include "bacnet/basic/object/time_value.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/object/lo.h"
#include "bacnet/basic/object/blo.h"
#include "bacnet/basic/object/netport.h"
#include "bacnet/basic/object/color_object.h"
#include "bacnet/basic/object/color_temperature.h"
#include "bacnet/basic/object/program.h"

#ifdef CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION
#define BACNET_DEVICE_VERSION CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION
#else
#define BACNET_DEVICE_VERSION "1.0.0"
#endif

#ifdef CONFIG_BACNET_BASIC_DEVICE_OBJECT_NAME
#define BACNET_DEVICE_OBJECT_NAME CONFIG_BACNET_BASIC_DEVICE_OBJECT_NAME
#else
#define BACNET_DEVICE_OBJECT_NAME "BACnet Basic Device"
#endif

#ifdef CONFIG_BACNET_BASIC_DEVICE_DESCRIPTION
#define BACNET_DEVICE_DESCRIPTION CONFIG_BACNET_BASIC_DEVICE_DESCRIPTION
#else
#define BACNET_DEVICE_DESCRIPTION "BACnet Basic Server Device"
#endif

#ifdef CONFIG_BACNET_BASIC_DEVICE_MODEL_NAME
#define BACNET_DEVICE_MODEL_NAME CONFIG_BACNET_BASIC_DEVICE_MODEL_NAME
#else
#define BACNET_DEVICE_MODEL_NAME "GNU Basic Server Model 42"
#endif

#ifdef CONFIG_BACNET_BASIC_DEVICE_LOCATION_NAME
#define BACNET_DEVICE_LOCATION_NAME CONFIG_BACNET_BASIC_DEVICE_LOCATION_NAME
#else
#define BACNET_DEVICE_LOCATION_NAME "GNU Basic Building"
#endif

#ifdef CONFIG_BACNET_BASIC_DEVICE_SERIAL_NUMBER
#define BACNET_DEVICE_SERIAL_NUMBER CONFIG_BACNET_BASIC_DEVICE_SERIAL_NUMBER
#else
#define BACNET_DEVICE_SERIAL_NUMBER "BACnetDMcN56RBkeDJuNfxn3M44tfC2Y"
#endif

#ifdef CONFIG_BACNET_BASIC_COV_SUBSCRIPTIONS_SIZE
#define BACNET_COV_SUBSCRIPTIONS_SIZE CONFIG_BACNET_BASIC_COV_SUBSCRIPTIONS_SIZE
#else
#define BACNET_COV_SUBSCRIPTIONS_SIZE 0
#endif

#if !(                                                            \
    defined(CONFIG_BACNET_BASIC_OBJECT_ALL) ||                    \
    defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT) ||          \
    defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT) ||          \
    defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT) ||       \
    defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT) ||      \
    defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE) ||       \
    defined(CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_CALENDAR) ||               \
    defined(CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE) ||          \
    defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT) ||      \
    defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE) ||       \
    defined(CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT) ||        \
    defined(CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL) ||           \
    defined(CONFIG_BACNET_BASIC_OBJECT_CHANNEL) ||                \
    defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT) || \
    defined(CONFIG_BACNET_BASIC_OBJECT_COLOR) ||                  \
    defined(CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE) ||      \
    defined(CONFIG_BACNET_BASIC_OBJECT_FILE) ||                   \
    defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW) ||        \
    defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) ||        \
    defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) ||                \
    defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE))
#define CONFIG_BACNET_BASIC_OBJECT_ALL
#endif

#if defined(CONFIG_BACNET_BASIC_OBJECT_ALL)
#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT
#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT
#define CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT
#define CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT
#define CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT
#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT
#define CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT
#define CONFIG_BACNET_BASIC_OBJECT_CALENDAR
#define CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT
#define CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE
#define CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT
#define CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL
#define CONFIG_BACNET_BASIC_OBJECT_CHANNEL
#define CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT
#define CONFIG_BACNET_BASIC_OBJECT_COLOR
#define CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE
#define CONFIG_BACNET_BASIC_OBJECT_FILE
#define CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW
#define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_PROGRAM
#define CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE
#endif

#if (BACNET_PROTOCOL_REVISION < 14)
#ifdef CONFIG_BACNET_BASIC_OBJECT_CHANNEL
#undef CONFIG_BACNET_BASIC_OBJECT_CHANNEL
#warning "Channel configured, but BACnet Protocol Revision < 14"
#endif
#ifdef CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT
#undef CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT
#warning "Lighting Output configured, but BACnet Protocol Revision < 14"
#endif
#endif

#if (BACNET_PROTOCOL_REVISION < 16)
#ifdef CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT
#undef CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT
#warning "Binary Lighting Output configured, but BACnet Protocol Revision < 16"
#endif
#endif

#if (BACNET_PROTOCOL_REVISION < 17)
#ifdef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT
#undef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT
#warning "Network Port is configured, but BACnet Protocol Revision < 17"
#endif
#endif

#if (BACNET_PROTOCOL_REVISION < 24)
#ifdef CONFIG_BACNET_BASIC_OBJECT_COLOR
#undef CONFIG_BACNET_BASIC_OBJECT_COLOR
#warning "Color configured, but BACnet Protocol Revision < 24"
#endif
#ifdef CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE
#undef CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE
#warning "Color Temperature configured, but BACnet Protocol Revision < 24"
#endif
#endif

/* may be overridden by outside table */
static object_functions_t *Object_Table;
static object_functions_t My_Object_Table[] = {
    { OBJECT_DEVICE,
      NULL, /* don't init - recursive! */
      Device_Count,
      Device_Index_To_Instance,
      Device_Valid_Object_Instance_Number,
      Device_Object_Name,
      Device_Read_Property_Local,
      Device_Write_Property_Local,
      Device_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      NULL /* Create */,
      NULL /* Delete */,
      NULL /* Timer */ },
#if defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_INPUT)
    { OBJECT_ANALOG_INPUT,
      Analog_Input_Init,
      Analog_Input_Count,
      Analog_Input_Index_To_Instance,
      Analog_Input_Valid_Instance,
      Analog_Input_Object_Name,
      Analog_Input_Read_Property,
      Analog_Input_Write_Property,
      Analog_Input_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Analog_Input_Encode_Value_List,
      Analog_Input_Change_Of_Value,
      Analog_Input_Change_Of_Value_Clear,
      Analog_Input_Intrinsic_Reporting,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Analog_Input_Create,
      Analog_Input_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_OUTPUT)
    { OBJECT_ANALOG_OUTPUT,
      Analog_Output_Init,
      Analog_Output_Count,
      Analog_Output_Index_To_Instance,
      Analog_Output_Valid_Instance,
      Analog_Output_Object_Name,
      Analog_Output_Read_Property,
      Analog_Output_Write_Property,
      Analog_Output_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Analog_Output_Encode_Value_List,
      Analog_Output_Change_Of_Value,
      Analog_Output_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Analog_Output_Create,
      Analog_Output_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_ANALOG_VALUE)
    { OBJECT_ANALOG_VALUE,
      Analog_Value_Init,
      Analog_Value_Count,
      Analog_Value_Index_To_Instance,
      Analog_Value_Valid_Instance,
      Analog_Value_Object_Name,
      Analog_Value_Read_Property,
      Analog_Value_Write_Property,
      Analog_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Analog_Value_Encode_Value_List,
      Analog_Value_Change_Of_Value,
      Analog_Value_Change_Of_Value_Clear,
      Analog_Value_Intrinsic_Reporting,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Analog_Value_Create,
      Analog_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_INPUT)
    { OBJECT_BINARY_INPUT,
      Binary_Input_Init,
      Binary_Input_Count,
      Binary_Input_Index_To_Instance,
      Binary_Input_Valid_Instance,
      Binary_Input_Object_Name,
      Binary_Input_Read_Property,
      Binary_Input_Write_Property,
      Binary_Input_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Binary_Input_Encode_Value_List,
      Binary_Input_Change_Of_Value,
      Binary_Input_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Binary_Input_Create,
      Binary_Input_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_OUTPUT)
    { OBJECT_BINARY_OUTPUT,
      Binary_Output_Init,
      Binary_Output_Count,
      Binary_Output_Index_To_Instance,
      Binary_Output_Valid_Instance,
      Binary_Output_Object_Name,
      Binary_Output_Read_Property,
      Binary_Output_Write_Property,
      Binary_Output_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Binary_Output_Encode_Value_List,
      Binary_Output_Change_Of_Value,
      Binary_Output_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Binary_Output_Create,
      Binary_Output_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_VALUE)
    { OBJECT_BINARY_VALUE,
      Binary_Value_Init,
      Binary_Value_Count,
      Binary_Value_Index_To_Instance,
      Binary_Value_Valid_Instance,
      Binary_Value_Object_Name,
      Binary_Value_Read_Property,
      Binary_Value_Write_Property,
      Binary_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Binary_Value_Encode_Value_List,
      Binary_Value_Change_Of_Value,
      Binary_Value_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Binary_Value_Create,
      Binary_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_INPUT)
    { OBJECT_MULTI_STATE_INPUT,
      Multistate_Input_Init,
      Multistate_Input_Count,
      Multistate_Input_Index_To_Instance,
      Multistate_Input_Valid_Instance,
      Multistate_Input_Object_Name,
      Multistate_Input_Read_Property,
      Multistate_Input_Write_Property,
      Multistate_Input_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Multistate_Input_Encode_Value_List,
      Multistate_Input_Change_Of_Value,
      Multistate_Input_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Multistate_Input_Create,
      Multistate_Input_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_OUTPUT)
    { OBJECT_MULTI_STATE_OUTPUT,
      Multistate_Output_Init,
      Multistate_Output_Count,
      Multistate_Output_Index_To_Instance,
      Multistate_Output_Valid_Instance,
      Multistate_Output_Object_Name,
      Multistate_Output_Read_Property,
      Multistate_Output_Write_Property,
      Multistate_Output_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Multistate_Output_Encode_Value_List,
      Multistate_Output_Change_Of_Value,
      Multistate_Output_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Multistate_Output_Create,
      Multistate_Output_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_MULTISTATE_VALUE)
    { OBJECT_MULTI_STATE_VALUE,
      Multistate_Value_Init,
      Multistate_Value_Count,
      Multistate_Value_Index_To_Instance,
      Multistate_Value_Valid_Instance,
      Multistate_Value_Object_Name,
      Multistate_Value_Read_Property,
      Multistate_Value_Write_Property,
      Multistate_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      Multistate_Value_Encode_Value_List,
      Multistate_Value_Change_Of_Value,
      Multistate_Value_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Multistate_Value_Create,
      Multistate_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT)
    { OBJECT_NETWORK_PORT,
      Network_Port_Init,
      Network_Port_Count,
      Network_Port_Index_To_Instance,
      Network_Port_Valid_Instance,
      Network_Port_Object_Name,
      Network_Port_Read_Property,
      Network_Port_Write_Property,
      Network_Port_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      NULL /* Create */,
      NULL /* Delete */,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_CALENDAR)
    { OBJECT_CALENDAR,
      Calendar_Init,
      Calendar_Count,
      Calendar_Index_To_Instance,
      Calendar_Valid_Instance,
      Calendar_Object_Name,
      Calendar_Read_Property,
      Calendar_Write_Property,
      Calendar_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Calendar_Create,
      Calendar_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE)
    { OBJECT_INTEGER_VALUE,
      Integer_Value_Init,
      Integer_Value_Count,
      Integer_Value_Index_To_Instance,
      Integer_Value_Valid_Instance,
      Integer_Value_Object_Name,
      Integer_Value_Read_Property,
      Integer_Value_Write_Property,
      Integer_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Integer_Value_Create,
      Integer_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT)
    { OBJECT_LIFE_SAFETY_POINT,
      Life_Safety_Point_Init,
      Life_Safety_Point_Count,
      Life_Safety_Point_Index_To_Instance,
      Life_Safety_Point_Valid_Instance,
      Life_Safety_Point_Object_Name,
      Life_Safety_Point_Read_Property,
      Life_Safety_Point_Write_Property,
      Life_Safety_Point_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Life_Safety_Point_Create,
      Life_Safety_Point_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_ZONE)
    { OBJECT_LIFE_SAFETY_ZONE,
      Life_Safety_Zone_Init,
      Life_Safety_Zone_Count,
      Life_Safety_Zone_Index_To_Instance,
      Life_Safety_Zone_Valid_Instance,
      Life_Safety_Zone_Object_Name,
      Life_Safety_Zone_Read_Property,
      Life_Safety_Zone_Write_Property,
      Life_Safety_Zone_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Life_Safety_Zone_Create,
      Life_Safety_Zone_Delete,
      NULL /* Timer */ },
#endif

#if defined(CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL)
    { OBJECT_LOAD_CONTROL,
      Load_Control_Init,
      Load_Control_Count,
      Load_Control_Index_To_Instance,
      Load_Control_Valid_Instance,
      Load_Control_Object_Name,
      Load_Control_Read_Property,
      Load_Control_Write_Property,
      Load_Control_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Load_Control_Create,
      Load_Control_Delete,
      Load_Control_Timer },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT)
    { OBJECT_LIGHTING_OUTPUT,
      Lighting_Output_Init,
      Lighting_Output_Count,
      Lighting_Output_Index_To_Instance,
      Lighting_Output_Valid_Instance,
      Lighting_Output_Object_Name,
      Lighting_Output_Read_Property,
      Lighting_Output_Write_Property,
      Lighting_Output_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Lighting_Output_Create,
      Lighting_Output_Delete,
      Lighting_Output_Timer },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_CHANNEL)
    { OBJECT_CHANNEL,
      Channel_Init,
      Channel_Count,
      Channel_Index_To_Instance,
      Channel_Valid_Instance,
      Channel_Object_Name,
      Channel_Read_Property,
      Channel_Write_Property,
      Channel_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Channel_Create,
      Channel_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT)
    { OBJECT_BINARY_LIGHTING_OUTPUT,
      Binary_Lighting_Output_Init,
      Binary_Lighting_Output_Count,
      Binary_Lighting_Output_Index_To_Instance,
      Binary_Lighting_Output_Valid_Instance,
      Binary_Lighting_Output_Object_Name,
      Binary_Lighting_Output_Read_Property,
      Binary_Lighting_Output_Write_Property,
      Binary_Lighting_Output_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Binary_Lighting_Output_Create,
      Binary_Lighting_Output_Delete,
      Binary_Lighting_Output_Timer },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_COLOR)
    { OBJECT_COLOR,
      Color_Init,
      Color_Count,
      Color_Index_To_Instance,
      Color_Valid_Instance,
      Color_Object_Name,
      Color_Read_Property,
      Color_Write_Property,
      Color_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Color_Create,
      Color_Delete,
      Color_Timer },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE)
    { OBJECT_COLOR_TEMPERATURE,
      Color_Temperature_Init,
      Color_Temperature_Count,
      Color_Temperature_Index_To_Instance,
      Color_Temperature_Valid_Instance,
      Color_Temperature_Object_Name,
      Color_Temperature_Read_Property,
      Color_Temperature_Write_Property,
      Color_Temperature_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Color_Temperature_Create,
      Color_Temperature_Delete,
      Color_Temperature_Timer },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_FILE)
    { OBJECT_FILE,
      bacfile_init,
      bacfile_count,
      bacfile_index_to_instance,
      bacfile_valid_instance,
      bacfile_object_name,
      bacfile_read_property,
      bacfile_write_property,
      BACfile_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      bacfile_create,
      bacfile_delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW)
    { OBJECT_STRUCTURED_VIEW,
      Structured_View_Init,
      Structured_View_Count,
      Structured_View_Index_To_Instance,
      Structured_View_Valid_Instance,
      Structured_View_Object_Name,
      Structured_View_Read_Property,
      NULL /* Write_Property */,
      Structured_View_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Structured_View_Create,
      Structured_View_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE)
    { OBJECT_BITSTRING_VALUE,
      BitString_Value_Init,
      BitString_Value_Count,
      BitString_Value_Index_To_Instance,
      BitString_Value_Valid_Instance,
      BitString_Value_Object_Name,
      BitString_Value_Read_Property,
      BitString_Value_Write_Property,
      BitString_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      BitString_Value_Encode_Value_List,
      BitString_Value_Change_Of_Value,
      BitString_Value_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      BitString_Value_Create,
      BitString_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE)
    { OBJECT_CHARACTERSTRING_VALUE,
      CharacterString_Value_Init,
      CharacterString_Value_Count,
      CharacterString_Value_Index_To_Instance,
      CharacterString_Value_Valid_Instance,
      CharacterString_Value_Object_Name,
      CharacterString_Value_Read_Property,
      CharacterString_Value_Write_Property,
      CharacterString_Value_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      CharacterString_Value_Encode_Value_List,
      CharacterString_Value_Change_Of_Value,
      CharacterString_Value_Change_Of_Value_Clear,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      CharacterString_Value_Create,
      CharacterString_Value_Delete,
      NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM)
    { OBJECT_BITSTRING_VALUE,
      Program_Init,
      Program_Count,
      Program_Index_To_Instance,
      Program_Valid_Instance,
      Program_Object_Name,
      Program_Read_Property,
      Program_Write_Property,
      Program_Property_Lists,
      NULL /* ReadRangeInfo */,
      NULL /* Iterator */,
      NULL /* Value_Lists */,
      NULL /* COV */,
      NULL /* COV Clear */,
      NULL /* Intrinsic Reporting */,
      NULL /* Add_List_Element */,
      NULL /* Remove_List_Element */,
      Program_Create,
      Program_Delete,
      Program_Timer },
#endif
    {
        MAX_BACNET_OBJECT_TYPE,
        NULL /* Init */,
        NULL /* Count */,
        NULL /* Index_To_Instance */,
        NULL /* Valid_Instance */,
        NULL /* Object_Name */,
        NULL /* Read_Property */,
        NULL /* Write_Property */,
        NULL /* Property_Lists */,
        NULL /* ReadRangeInfo */,
        NULL /* Iterator */,
        NULL /* Value_Lists */,
        NULL /* COV */,
        NULL /* COV Clear */,
        NULL /* Intrinsic Reporting */,
        NULL /* Add_List_Element */,
        NULL /* Remove_List_Element */,
        NULL /* Create */,
        NULL /* Delete */,
        NULL /* Timer */
    }
};

/** Glue function to let the Device object, when called by a handler,
 * lookup which Object type needs to be invoked.
 * @ingroup ObjHelpers
 * @param Object_Type [in] The type of BACnet Object the handler wants to
 * access.
 * @return Pointer to the group of object helper functions that implement this
 *         type of Object.
 */
struct object_functions *
Device_Object_Functions_Find(BACNET_OBJECT_TYPE Object_Type)
{
    struct object_functions *pObject = NULL;

    pObject = Object_Table;
    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
        /* handle each object type */
        if (pObject->Object_Type == Object_Type) {
            return (pObject);
        }
        pObject++;
    }

    return (NULL);
}

/** Try to find a rr_info_function helper function for the requested object
 * type.
 * @ingroup ObjIntf
 *
 * @param object_type [in] The type of BACnet Object the handler wants to
 * access.
 * @return Pointer to the object helper function that implements the
 *         ReadRangeInfo function, Object_RR_Info, for this type of Object on
 *         success, else a NULL pointer if the type of Object isn't supported
 *         or doesn't have a ReadRangeInfo function.
 */
rr_info_function Device_Objects_RR_Info(BACNET_OBJECT_TYPE object_type)
{
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(object_type);
    return (pObject != NULL ? pObject->Object_RR_Info : NULL);
}

/** For a given object type, returns the special property list.
 * This function is used for ReadPropertyMultiple calls which want
 * just Required, just Optional, or All properties.
 * @ingroup ObjIntf
 *
 * @param object_type [in] The desired BACNET_OBJECT_TYPE whose properties
 *            are to be listed.
 * @param pPropertyList [out] Reference to the structure which will, on return,
 *            list, separately, the Required, Optional, and Proprietary object
 *            properties with their counts.
 */
void Device_Objects_Property_List(
    BACNET_OBJECT_TYPE object_type,
    uint32_t object_instance,
    struct special_property_list_t *pPropertyList)
{
    struct object_functions *pObject = NULL;

    (void)object_instance;
    pPropertyList->Required.pList = NULL;
    pPropertyList->Optional.pList = NULL;
    pPropertyList->Proprietary.pList = NULL;

    /* If we can find an entry for the required object type
     * and there is an Object_List_RPM fn ptr then call it
     * to populate the pointers to the individual list counters.
     */

    pObject = Device_Object_Functions_Find(object_type);
    if ((pObject != NULL) && (pObject->Object_RPM_List != NULL)) {
        pObject->Object_RPM_List(
            &pPropertyList->Required.pList, &pPropertyList->Optional.pList,
            &pPropertyList->Proprietary.pList);
    }

    /* Fetch the counts if available otherwise zero them */
    pPropertyList->Required.count = pPropertyList->Required.pList == NULL
        ? 0
        : property_list_count(pPropertyList->Required.pList);

    pPropertyList->Optional.count = pPropertyList->Optional.pList == NULL
        ? 0
        : property_list_count(pPropertyList->Optional.pList);

    pPropertyList->Proprietary.count = pPropertyList->Proprietary.pList == NULL
        ? 0
        : property_list_count(pPropertyList->Proprietary.pList);

    return;
}

/* These three arrays are used by the ReadPropertyMultiple handler */
static const int Device_Properties_Required[] = {
    /* List of Required properties in this object */
    PROP_OBJECT_IDENTIFIER,
    PROP_OBJECT_NAME,
    PROP_OBJECT_TYPE,
    PROP_SYSTEM_STATUS,
    PROP_VENDOR_NAME,
    PROP_VENDOR_IDENTIFIER,
    PROP_MODEL_NAME,
    PROP_FIRMWARE_REVISION,
    PROP_APPLICATION_SOFTWARE_VERSION,
    PROP_PROTOCOL_VERSION,
    PROP_PROTOCOL_REVISION,
    PROP_PROTOCOL_SERVICES_SUPPORTED,
    PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED,
    PROP_OBJECT_LIST,
    PROP_MAX_APDU_LENGTH_ACCEPTED,
    PROP_SEGMENTATION_SUPPORTED,
    PROP_APDU_TIMEOUT,
    PROP_NUMBER_OF_APDU_RETRIES,
    PROP_DEVICE_ADDRESS_BINDING,
    PROP_DATABASE_REVISION,
    -1
};

static const int Device_Properties_Optional[] = {
/* List of Optional properties in this object */
#if defined(BACDL_MSTP)
    PROP_MAX_MASTER,
    PROP_MAX_INFO_FRAMES,
#endif
    PROP_DESCRIPTION,
    PROP_LOCAL_TIME,
    PROP_UTC_OFFSET,
    PROP_LOCAL_DATE,
    PROP_DAYLIGHT_SAVINGS_STATUS,
    PROP_LOCATION,
#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0)
    PROP_ACTIVE_COV_SUBSCRIPTIONS,
#endif
#if defined(BACNET_TIME_MASTER)
    PROP_TIME_SYNCHRONIZATION_RECIPIENTS,
    PROP_TIME_SYNCHRONIZATION_INTERVAL,
    PROP_ALIGN_INTERVALS,
    PROP_INTERVAL_OFFSET,
#endif
    PROP_SERIAL_NUMBER,
    PROP_TIME_OF_DEVICE_RESTART,
    -1
};

static const int Device_Properties_Proprietary[] = {
    /* List of Proprietary properties in this object */
    -1
};

/**
 * @brief Returns the list of required, optional, and proprietary properties
 *       for the Device object.
 * @param pRequired [out] Pointer to the list of required properties
 * @param pOptional [out] Pointer to the list of optional properties
 * @param pProprietary [out] Pointer to the list of proprietary properties
 * @note The lists are terminated with -1.
 * @note The lists are not allocated, so do not free them.
 * @note The lists are static, so do not modify them.
 * @ingroup ObjIntf
 */
void Device_Property_Lists(
    const int **pRequired, const int **pOptional, const int **pProprietary)
{
    if (pRequired) {
        *pRequired = Device_Properties_Required;
    }
    if (pOptional) {
        *pOptional = Device_Properties_Optional;
    }
    if (pProprietary) {
        *pProprietary = Device_Properties_Proprietary;
    }

    return;
}

/**
 * @brief Determine if the object property is a member of this object instance
 * @param object_type - object type of the object
 * @param object_instance - object-instance number of the object
 * @param object_property - object-property to be checked
 * @return true if the property is a member of this object instance
 */
bool Device_Objects_Property_List_Member(
    BACNET_OBJECT_TYPE object_type,
    uint32_t object_instance,
    BACNET_PROPERTY_ID object_property)
{
    bool found = false;
    struct special_property_list_t property_list = { 0 };

    Device_Objects_Property_List(object_type, object_instance, &property_list);
    found = property_list_member(property_list.Required.pList, object_property);
    if (!found) {
        found =
            property_list_member(property_list.Optional.pList, object_property);
    }
    if (!found) {
        found = property_list_member(
            property_list.Proprietary.pList, object_property);
    }

    return found;
}

/* note: you really only need to define variables for
   properties that are writable or that may change.
   The properties that are constant can be hard coded
   into the read-property encoding. */

/* local data */
static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE;
static BACNET_CHARACTER_STRING My_Object_Name;
static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
static const char *Device_Name_Default = BACNET_DEVICE_OBJECT_NAME;
static const char *Device_Vendor_Name_Default = BACNET_VENDOR_NAME;
static uint16_t Vendor_Identifier = BACNET_VENDOR_ID;
static const char *Model_Name = BACNET_DEVICE_MODEL_NAME;
static const char *Application_Software_Version = BACNET_DEVICE_VERSION;
static const char *Firmware_Revision = BACNET_VERSION_TEXT;
static const char *Device_Location_Default = BACNET_DEVICE_LOCATION_NAME;
static const char *Device_Description_Default = BACNET_DEVICE_DESCRIPTION;
static uint32_t Database_Revision;
static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE;
static BACNET_CHARACTER_STRING Reinit_Password;
static write_property_function Device_Write_Property_Store_Callback;
static uint8_t Device_UUID[16];
static const char *Serial_Number = BACNET_DEVICE_SERIAL_NUMBER;
static BACNET_TIMESTAMP Time_Of_Device_Restart;
static BACNET_TIME Local_Time; /* rely on OS, if there is one */
static BACNET_DATE Local_Date; /* rely on OS, if there is one */
/* NOTE: BACnet UTC Offset is inverse of common practice.
   If your UTC offset is -5hours of GMT,
   then BACnet UTC offset is +5hours.
   BACnet UTC offset is expressed in minutes. */
static int16_t UTC_Offset = 5 * 60;
static bool Daylight_Savings_Status = false; /* rely on OS */
#if defined(BACNET_TIME_MASTER)
static bool Align_Intervals;
static uint32_t Interval_Minutes;
static uint32_t Interval_Offset_Minutes;
/* Time_Synchronization_Recipients */
#endif

/**
 * @brief Sets the ReinitializeDevice password
 *
 * The password shall be a null terminated C string of up to
 * 20 ASCII characters for those devices that require the password.
 *
 * For those devices that do not require a password, set to NULL or
 * point to a zero length C string (null terminated).
 *
 * @param the ReinitializeDevice password; can be NULL or empty string
 */
bool Device_Reinitialize_Password_Set(const char *password)
{
    characterstring_init_ansi(&Reinit_Password, password);

    return true;
}

/**
 * @brief Commands a Device re-initialization, to a given state.
 *  The request's password must match for the operation to succeed.
 *  This implementation provides a framework, but doesn't
 *  actually *DO* anything.
 * @note You could use a mix of states and passwords to multiple outcomes.
 * @note You probably want to restart *after* the simple ack has been sent
 *       from the return handler, so just set a local flag here.
 * @ingroup ObjIntf
 *
 * @param rd_data [in,out] The information from the RD request.
 *                         On failure, the error class and code will be set.
 * @return True if succeeds (password is correct), else False.
 */
bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data)
{
    bool status = false;
    bool password_success = false;
    unsigned i;

    /* From 16.4.1.1.2 Password
        This optional parameter shall be a CharacterString of up to
        20 characters. For those devices that require the password as a
        protection, the service request shall be denied if the parameter
        is absent or if the password is incorrect. For those devices that
        do not require a password, this parameter shall be ignored.*/
    if (characterstring_length(&Reinit_Password) > 0) {
        if (characterstring_length(&rd_data->password) > 20) {
            rd_data->error_class = ERROR_CLASS_SERVICES;
            rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE;
        } else if (characterstring_same(&rd_data->password, &Reinit_Password)) {
            password_success = true;
        } else {
            rd_data->error_class = ERROR_CLASS_SECURITY;
            rd_data->error_code = ERROR_CODE_PASSWORD_FAILURE;
        }
    } else {
        password_success = true;
    }
    if (password_success) {
        switch (rd_data->state) {
            case BACNET_REINIT_COLDSTART:
            case BACNET_REINIT_WARMSTART:
                dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
                /* Note: you could use a mix of state
                       and password to multiple things */
                /* note: you probably want to restart *after* the
                       simple ack has been sent from the return handler
                       so just set a flag from here */
                Reinitialize_State = rd_data->state;
                status = true;
                break;
            case BACNET_REINIT_STARTBACKUP:
            case BACNET_REINIT_ENDBACKUP:
            case BACNET_REINIT_STARTRESTORE:
            case BACNET_REINIT_ENDRESTORE:
            case BACNET_REINIT_ABORTRESTORE:
                if (dcc_communication_disabled()) {
                    rd_data->error_class = ERROR_CLASS_SERVICES;
                    rd_data->error_code = ERROR_CODE_COMMUNICATION_DISABLED;
                } else {
                    rd_data->error_class = ERROR_CLASS_SERVICES;
                    rd_data->error_code =
                        ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
                }
                break;
            case BACNET_REINIT_ACTIVATE_CHANGES:
                /* note: activate changes *after* the simple ack is sent */
                for (i = 0; i < Network_Port_Count(); i++) {
                    Network_Port_Changes_Pending_Activate(
                        Network_Port_Index_To_Instance(i));
                }
                Reinitialize_State = rd_data->state;
                status = true;
                break;
            default:
                rd_data->error_class = ERROR_CLASS_SERVICES;
                rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE;
                break;
        }
    }

    return status;
}

BACNET_REINITIALIZED_STATE Device_Reinitialized_State(void)
{
    return Reinitialize_State;
}

bool Device_Reinitialize_State_Set(BACNET_REINITIALIZED_STATE state)
{
    Reinitialize_State = state;
    return true;
}

unsigned Device_Count(void)
{
    return 1;
}

uint32_t Device_Index_To_Instance(unsigned index)
{
    (void)index;
    return Object_Instance_Number;
}

/** Return the Object Instance number for our (single) Device Object.
 * This is a key function, widely invoked by the handler code, since
 * it provides "our" (ie, local) address.
 * @ingroup ObjIntf
 * @return The Instance number used in the BACNET_OBJECT_ID for the Device.
 */
uint32_t Device_Object_Instance_Number(void)
{
    return Object_Instance_Number;
}

bool Device_Set_Object_Instance_Number(uint32_t object_id)
{
    bool status = true; /* return value */

    if (object_id <= BACNET_MAX_INSTANCE) {
        Object_Instance_Number = object_id;
    } else {
        status = false;
    }

    return status;
}

bool Device_Valid_Object_Instance_Number(uint32_t object_id)
{
    return (Object_Instance_Number == object_id);
}

bool Device_Object_Name(
    uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
{
    bool status = false;

    if (object_instance == Object_Instance_Number) {
        status = characterstring_copy(object_name, &My_Object_Name);
    }

    return status;
}

bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name)
{
    bool status = false; /*return value */

    if (!characterstring_same(&My_Object_Name, object_name)) {
        /* Make the change and update the database revision */
        status = characterstring_copy(&My_Object_Name, object_name);
        Device_Inc_Database_Revision();
    }

    return status;
}

/**
 * @brief Initialize the Device Object Name with an ANSI C string
 * @param value [in] The object name as a null-terminated string
 * @return True on success, else False
 */
bool Device_Object_Name_ANSI_Init(const char *value)
{
    return characterstring_init_ansi(&My_Object_Name, value);
}

/**
 * @brief Get the Device Object Name as a C string
 * @return The object name as a null-terminated string
 */
char *Device_Object_Name_ANSI(void)
{
    return (char *)characterstring_value(&My_Object_Name);
}

/**
 * @brief Initialize a UUID for storing the unique identifier of this device
 * @note A Universally Unique IDentifier (UUID) - also called a
 * Global Unique IDentifier (GUID) - is a 128-bit value, see RFC 4122.
 *
 * 4.4.  Algorithms for Creating a UUID from Truly Random or
 *      Pseudo-Random Numbers
 *
 *   The version 4 UUID is meant for generating UUIDs from truly-random or
 *   pseudo-random numbers.
 *
 *   The algorithm is as follows:
 *
 *   o  Set the two most significant bits (bits 6 and 7) of the
 *      clock_seq_hi_and_reserved to zero and one, respectively.
 *
 *   o  Set the four most significant bits (bits 12 through 15) of the
 *      time_hi_and_version field to the 4-bit version number from
 *      Section 4.1.3.
 *
 *   o  Set all the other bits to randomly (or pseudo-randomly) chosen
 *      values.
 */
void Device_UUID_Init(void)
{
    unsigned i = 0;

    /* 1. Generate 16 random bytes = 128 bits */
    for (i = 0; i < sizeof(Device_UUID); i++) {
        Device_UUID[i] = rand() % 256;
    }
    /* 2. Adjust certain bits according to RFC 4122 section 4.4.
       This just means do the following
       (a) set the high nibble of the 7th byte equal to 4 and
       (b) set the two most significant bits of the 9th byte to 10'B,
       so the high nibble will be one of {8,9,A,B}.
       From http://www.cryptosys.net/pki/Uuid.c.html */
    Device_UUID[6] = 0x40 | (Device_UUID[6] & 0x0f);
    Device_UUID[8] = 0x80 | (Device_UUID[8] & 0x3f);
}

/**
 * @brief Set the UUID for this device
 * @param new_uuid [in] The new UUID to set
 * @param length [in] The length of the new UUID
 */
void Device_UUID_Set(const uint8_t *new_uuid, size_t length)
{
    if (new_uuid && (length == sizeof(Device_UUID))) {
        memcpy(Device_UUID, new_uuid, sizeof(Device_UUID));
    }
}

/**
 * @brief Get the UUID for this device
 * @param uuid [out] The UUID of this device
 * @param length [in] The length of the UUID
 */
void Device_UUID_Get(uint8_t *uuid, size_t length)
{
    if (uuid && (length == sizeof(Device_UUID))) {
        memcpy(uuid, Device_UUID, sizeof(Device_UUID));
    }
}

BACNET_DEVICE_STATUS Device_System_Status(void)
{
    return System_Status;
}

int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local)
{
    /*return value - 0 = ok, -1 = bad value, -2 = not allowed */
    int result = -1;

    (void)local;
    if (status < MAX_DEVICE_STATUS) {
        System_Status = status;
        result = 0;
    }

    return result;
}

const char *Device_Vendor_Name(void)
{
    return Device_Vendor_Name_Default;
}

bool Device_Set_Vendor_Name(const char *name, size_t length)
{
    (void)length;
    Device_Vendor_Name_Default = name ? name : BACNET_VENDOR_NAME;

    return true;
}

uint16_t Device_Vendor_Identifier(void)
{
    return Vendor_Identifier;
}

void Device_Set_Vendor_Identifier(uint16_t vendor_id)
{
    Vendor_Identifier = vendor_id;
}

const char *Device_Model_Name(void)
{
    return Model_Name;
}

bool Device_Set_Model_Name(const char *name, size_t length)
{
    (void)length;
    Model_Name = name ? name : BACNET_DEVICE_MODEL_NAME;

    return true;
}

const char *Device_Firmware_Revision(void)
{
    return Firmware_Revision;
}

bool Device_Set_Firmware_Revision(const char *name, size_t length)
{
    (void)length;
    Firmware_Revision = name ? name : BACNET_VERSION_TEXT;

    return true;
}

const char *Device_Application_Software_Version(void)
{
    return Application_Software_Version;
}

bool Device_Set_Application_Software_Version(const char *name, size_t length)
{
    (void)length;
    Application_Software_Version = name ? name : BACNET_DEVICE_VERSION;

    return true;
}

const char *Device_Description(void)
{
    return Device_Description_Default;
}

bool Device_Set_Description(const char *name, size_t length)
{
    (void)length;
    Device_Description_Default = name ? name : BACNET_DEVICE_DESCRIPTION;

    return true;
}

const char *Device_Location(void)
{
    return Device_Location_Default;
}

bool Device_Set_Location(const char *name, size_t length)
{
    (void)length;
    Device_Location_Default = name ? name : BACNET_DEVICE_LOCATION_NAME;
    return true;
}

/**
 * @brief Get the device serial-number property value.
 * @return The device serial-number, as a character string.
 */
const char *Device_Serial_Number(void)
{
    return Serial_Number;
}

/**
 * @brief Set the device serial-number property value.
 * @param str [in] The new device serial-number, as a character string.
 * @param length [in] The number of characters in the string.
 * @return true if the device serial-number was set, false if the value was
 *  too long to store in the object.
 */
bool Device_Serial_Number_Set(const char *str, size_t length)
{
    (void)length;
    Serial_Number = str ? str : BACNET_DEVICE_SERIAL_NUMBER;
    return true;
}

void Device_Time_Of_Restart(BACNET_TIMESTAMP *time_of_restart)
{
    bacapp_timestamp_copy(time_of_restart, &Time_Of_Device_Restart);
}

bool Device_Set_Time_Of_Restart(const BACNET_TIMESTAMP *time_of_restart)
{
    bool status = false;

    if (time_of_restart) {
        bacapp_timestamp_copy(&Time_Of_Device_Restart, time_of_restart);
        status = true;
    }

    return status;
}

uint8_t Device_Protocol_Version(void)
{
    return BACNET_PROTOCOL_VERSION;
}

uint8_t Device_Protocol_Revision(void)
{
    return BACNET_PROTOCOL_REVISION;
}

BACNET_SEGMENTATION Device_Segmentation_Supported(void)
{
    return SEGMENTATION_NONE;
}

/**
 * @brief Get the Database Revision property of the Device Object
 * @return The Database Revision property of the Device Object
 */
uint32_t Device_Database_Revision(void)
{
    return Database_Revision;
}

/**
 * @brief Set the Database Revision property of the Device Object
 * @param revision [in] The new value for the Database Revision property
 */
void Device_Set_Database_Revision(uint32_t revision)
{
    Database_Revision = revision;
}

/*
 * Shortcut for incrementing database revision as this is potentially
 * the most common operation if changing object names and ids is
 * implemented.
 */
void Device_Inc_Database_Revision(void)
{
    Database_Revision++;
}

/** Get the total count of objects supported by this Device Object.
 * @note Since many network clients depend on the object list
 *       for discovery, it must be consistent!
 * @return The count of objects, for all supported Object types.
 */
unsigned Device_Object_List_Count(void)
{
    unsigned count = 0; /* number of objects */
    struct object_functions *pObject = NULL;

    /* initialize the default return values */
    pObject = Object_Table;
    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
        if (pObject->Object_Count) {
            count += pObject->Object_Count();
        }
        pObject++;
    }

    return count;
}

/** Lookup the Object at the given array index in the Device's Object List.
 * Even though we don't keep a single linear array of objects in the Device,
 * this method acts as though we do and works through a virtual, concatenated
 * array of all of our object type arrays.
 *
 * @param array_index [in] The desired array index (1 to N)
 * @param object_type [out] The object's type, if found.
 * @param instance [out] The object's instance number, if found.
 * @return True if found, else false.
 */
bool Device_Object_List_Identifier(
    uint32_t array_index, BACNET_OBJECT_TYPE *object_type, uint32_t *instance)
{
    bool status = false;
    uint32_t count = 0;
    uint32_t object_index = 0;
    struct object_functions *pObject = NULL;

    /* array index zero is length - so invalid */
    if (array_index == 0) {
        return status;
    }
    object_index = array_index - 1;
    /* initialize the default return values */
    pObject = &Object_Table[0];
    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
        if (pObject->Object_Count && pObject->Object_Index_To_Instance) {
            object_index -= count;
            count = pObject->Object_Count();
            if (object_index < count) {
                *object_type = pObject->Object_Type;
                *instance = pObject->Object_Index_To_Instance(object_index);
                status = true;
                break;
            }
        }
        pObject++;
    }

    return status;
}

/**
 * @brief Encode a BACnetARRAY property element
 * @param object_instance [in] BACnet network port object instance number
 * @param array_index [in] array index requested:
 *    0 to N for individual array members
 * @param apdu [out] Buffer in which the APDU contents are built, or NULL to
 * return the length of buffer if it had been built
 * @return The length of the apdu encoded or
 *   BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX
 */
int Device_Object_List_Element_Encode(
    uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu)
{
    int apdu_len = BACNET_STATUS_ERROR;
    BACNET_OBJECT_TYPE object_type;
    uint32_t instance;
    bool found;

    if (object_instance == Device_Object_Instance_Number()) {
        /* single element is zero based, add 1 for BACnetARRAY which is one
         * based */
        array_index++;
        found =
            Device_Object_List_Identifier(array_index, &object_type, &instance);
        if (found) {
            apdu_len =
                encode_application_object_id(apdu, object_type, instance);
        }
    }

    return apdu_len;
}

/** Determine if we have an object with the given object_name.
 * If the object_type and object_instance pointers are not null,
 * and the lookup succeeds, they will be given the resulting values.
 * @param object_name [in] The desired Object Name to look for.
 * @param object_type [out] The BACNET_OBJECT_TYPE of the matching Object.
 * @param object_instance [out] The object instance number of the matching
 * Object.
 * @return True on success or else False if not found.
 */
bool Device_Valid_Object_Name(
    const BACNET_CHARACTER_STRING *object_name1,
    BACNET_OBJECT_TYPE *object_type,
    uint32_t *object_instance)
{
    bool found = false;
    BACNET_OBJECT_TYPE type = OBJECT_NONE;
    uint32_t instance;
    uint32_t max_objects = 0, i = 0;
    bool check_id = false;
    BACNET_CHARACTER_STRING object_name2;
    struct object_functions *pObject = NULL;

    max_objects = Device_Object_List_Count();
    for (i = 1; i <= max_objects; i++) {
        check_id = Device_Object_List_Identifier(i, &type, &instance);
        if (check_id) {
            pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)type);
            if ((pObject != NULL) && (pObject->Object_Name != NULL) &&
                (pObject->Object_Name(instance, &object_name2) &&
                 characterstring_same(object_name1, &object_name2))) {
                found = true;
                if (object_type) {
                    *object_type = type;
                }
                if (object_instance) {
                    *object_instance = instance;
                }
                break;
            }
        }
    }

    return found;
}

/** Determine if we have an object of this type and instance number.
 * @param object_type [in] The desired BACNET_OBJECT_TYPE
 * @param object_instance [in] The object instance number to be looked up.
 * @return True if found, else False if no such Object in this device.
 */
bool Device_Valid_Object_Id(
    BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
    bool status = false; /* return value */
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)object_type);
    if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) {
        status = pObject->Object_Valid_Instance(object_instance);
    }

    return status;
}

/** Copy a child object's object_name value, given its ID.
 * @param object_type [in] The BACNET_OBJECT_TYPE of the child Object.
 * @param object_instance [in] The object instance number of the child Object.
 * @param object_name [out] The Object Name found for this child Object.
 * @return True on success or else False if not found.
 */
bool Device_Object_Name_Copy(
    BACNET_OBJECT_TYPE object_type,
    uint32_t object_instance,
    BACNET_CHARACTER_STRING *object_name)
{
    struct object_functions *pObject = NULL;
    bool found = false;

    pObject = Device_Object_Functions_Find(object_type);
    if ((pObject != NULL) && (pObject->Object_Name != NULL)) {
        found = pObject->Object_Name(object_instance, object_name);
    }

    return found;
}

static void Update_Current_Time(void)
{
    datetime_local(
        &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status);
}

void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime)
{
    Update_Current_Time();

    DateTime->date = Local_Date;
    DateTime->time = Local_Time;
}

int32_t Device_UTC_Offset(void)
{
    Update_Current_Time();

    return UTC_Offset;
}

void Device_UTC_Offset_Set(int16_t offset)
{
    UTC_Offset = offset;
}

bool Device_Daylight_Savings_Status(void)
{
    return Daylight_Savings_Status;
}

#if defined(BACNET_TIME_MASTER)
/**
 * Sets the time sync interval in minutes
 *
 * @param flag
 * This property, of type BOOLEAN, specifies whether (TRUE)
 * or not (FALSE) clock-aligned periodic time synchronization is
 * enabled. If periodic time synchronization is enabled and the
 * time synchronization interval is a factor of (divides without
 * remainder) an hour or day, then the beginning of the period
 * specified for time synchronization shall be aligned to the hour or
 * day, respectively. If this property is present, it shall be writable.
 */
bool Device_Align_Intervals_Set(bool flag)
{
    Align_Intervals = flag;

    return true;
}

bool Device_Align_Intervals(void)
{
    return Align_Intervals;
}

/**
 * Sets the time sync interval in minutes
 *
 * @param minutes
 * This property, of type Unsigned, specifies the periodic
 * interval in minutes at which TimeSynchronization and
 * UTCTimeSynchronization requests shall be sent. If this
 * property has a value of zero, then periodic time synchronization is
 * disabled. If this property is present, it shall be writable.
 */
bool Device_Time_Sync_Interval_Set(uint32_t minutes)
{
    Interval_Minutes = minutes;

    return true;
}

uint32_t Device_Time_Sync_Interval(void)
{
    return Interval_Minutes;
}

/**
 * Sets the time sync interval offset value.
 *
 * @param minutes
 * This property, of type Unsigned, specifies the offset in
 * minutes from the beginning of the period specified for time
 * synchronization until the actual time synchronization requests
 * are sent. The offset used shall be the value of Interval_Offset
 * modulo the value of Time_Synchronization_Interval;
 * e.g., if Interval_Offset has the value 31 and
 * Time_Synchronization_Interval is 30, the offset used shall be 1.
 * Interval_Offset shall have no effect if Align_Intervals is
 * FALSE. If this property is present, it shall be writable.
 */
bool Device_Interval_Offset_Set(uint32_t minutes)
{
    Interval_Offset_Minutes = minutes;

    return true;
}

uint32_t Device_Interval_Offset(void)
{
    return Interval_Offset_Minutes;
}
#endif

/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
   BACNET_STATUS_ABORT for abort message */
int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
{
    int apdu_len = 0; /* return value */
    BACNET_BIT_STRING bit_string = { 0 };
    BACNET_CHARACTER_STRING char_string = { 0 };
    uint32_t i = 0;
    uint32_t count = 0;
    uint8_t *apdu = NULL;
    struct object_functions *pObject = NULL;
    uint16_t apdu_max = 0;

    if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
        (rpdata->application_data_len == 0)) {
        return 0;
    }
    apdu = rpdata->application_data;
    apdu_max = rpdata->application_data_len;
    switch ((int)rpdata->object_property) {
        case PROP_OBJECT_IDENTIFIER:
            apdu_len = encode_application_object_id(
                &apdu[0], OBJECT_DEVICE, Object_Instance_Number);
            break;
        case PROP_OBJECT_NAME:
            apdu_len =
                encode_application_character_string(&apdu[0], &My_Object_Name);
            break;
        case PROP_OBJECT_TYPE:
            apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE);
            break;
        case PROP_DESCRIPTION:
            characterstring_init_ansi(&char_string, Device_Description_Default);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_SYSTEM_STATUS:
            apdu_len =
                encode_application_enumerated(&apdu[0], Device_System_Status());
            break;
        case PROP_VENDOR_NAME:
            characterstring_init_ansi(&char_string, Device_Vendor_Name_Default);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_VENDOR_IDENTIFIER:
            apdu_len = encode_application_unsigned(&apdu[0], Vendor_Identifier);
            break;
        case PROP_MODEL_NAME:
            characterstring_init_ansi(&char_string, Model_Name);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_FIRMWARE_REVISION:
            characterstring_init_ansi(&char_string, Firmware_Revision);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_APPLICATION_SOFTWARE_VERSION:
            characterstring_init_ansi(
                &char_string, Application_Software_Version);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_LOCATION:
            characterstring_init_ansi(&char_string, Device_Location_Default);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_LOCAL_TIME:
            Update_Current_Time();
            apdu_len = encode_application_time(&apdu[0], &Local_Time);
            break;
        case PROP_UTC_OFFSET:
            Update_Current_Time();
            apdu_len = encode_application_signed(&apdu[0], UTC_Offset);
            break;
        case PROP_LOCAL_DATE:
            Update_Current_Time();
            apdu_len = encode_application_date(&apdu[0], &Local_Date);
            break;
        case PROP_DAYLIGHT_SAVINGS_STATUS:
            Update_Current_Time();
            apdu_len =
                encode_application_boolean(&apdu[0], Daylight_Savings_Status);
            break;
        case PROP_PROTOCOL_VERSION:
            apdu_len = encode_application_unsigned(
                &apdu[0], Device_Protocol_Version());
            break;
        case PROP_PROTOCOL_REVISION:
            apdu_len = encode_application_unsigned(
                &apdu[0], Device_Protocol_Revision());
            break;
        case PROP_PROTOCOL_SERVICES_SUPPORTED:
            /* Note: list of services that are executed, not initiated. */
            bitstring_init(&bit_string);
            for (i = 0; i < MAX_BACNET_SERVICES_SUPPORTED; i++) {
                /* automatic lookup based on handlers set */
                bitstring_set_bit(
                    &bit_string, (uint8_t)i,
                    apdu_service_supported((BACNET_SERVICES_SUPPORTED)i));
            }
            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
            break;
        case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED:
            /* Note: this is the list of objects that can be in this device,
               not a list of objects that this device can access */
            bitstring_init(&bit_string);
            for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) {
                /* initialize all the object types to not-supported */
                bitstring_set_bit(&bit_string, (uint8_t)i, false);
            }
            /* set the object types with objects to supported */

            pObject = Object_Table;
            while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
                if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) {
                    bitstring_set_bit(
                        &bit_string, (uint8_t)pObject->Object_Type, true);
                }
                pObject++;
            }
            apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
            break;
        case PROP_OBJECT_LIST:
            count = Device_Object_List_Count();
            apdu_len = bacnet_array_encode(
                rpdata->object_instance, rpdata->array_index,
                Device_Object_List_Element_Encode, count, apdu, apdu_max);
            if (apdu_len == BACNET_STATUS_ABORT) {
                rpdata->error_code =
                    ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
            } else if (apdu_len == BACNET_STATUS_ERROR) {
                rpdata->error_class = ERROR_CLASS_PROPERTY;
                rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
            }
            break;
        case PROP_MAX_APDU_LENGTH_ACCEPTED:
            apdu_len = encode_application_unsigned(&apdu[0], MAX_APDU);
            break;
        case PROP_SEGMENTATION_SUPPORTED:
            apdu_len = encode_application_enumerated(
                &apdu[0], Device_Segmentation_Supported());
            break;
        case PROP_APDU_TIMEOUT:
            apdu_len = encode_application_unsigned(&apdu[0], apdu_timeout());
            break;
        case PROP_NUMBER_OF_APDU_RETRIES:
            apdu_len = encode_application_unsigned(&apdu[0], apdu_retries());
            break;
        case PROP_DEVICE_ADDRESS_BINDING:
            apdu_len = address_list_encode(&apdu[0], apdu_max);
            break;
        case PROP_DATABASE_REVISION:
            apdu_len = encode_application_unsigned(
                &apdu[0], Device_Database_Revision());
            break;
#if defined(BACDL_MSTP)
        case PROP_MAX_INFO_FRAMES:
            apdu_len =
                encode_application_unsigned(&apdu[0], dlmstp_max_info_frames());
            break;
        case PROP_MAX_MASTER:
            apdu_len =
                encode_application_unsigned(&apdu[0], dlmstp_max_master());
            break;
#endif
#if defined(BACNET_TIME_MASTER)
        case PROP_TIME_SYNCHRONIZATION_RECIPIENTS:
            apdu_len = handler_timesync_encode_recipients(&apdu[0], MAX_APDU);
            if (apdu_len < 0) {
                rpdata->error_code =
                    ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
                apdu_len = BACNET_STATUS_ABORT;
            }
            break;
        case PROP_TIME_SYNCHRONIZATION_INTERVAL:
            apdu_len = encode_application_unsigned(
                &apdu[0], Device_Time_Sync_Interval());
            break;
        case PROP_ALIGN_INTERVALS:
            apdu_len =
                encode_application_boolean(&apdu[0], Device_Align_Intervals());
            break;
        case PROP_INTERVAL_OFFSET:
            apdu_len =
                encode_application_unsigned(&apdu[0], Device_Interval_Offset());
            break;
#endif
#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0)
        case PROP_ACTIVE_COV_SUBSCRIPTIONS:
            if ((apdu_len = handler_cov_encode_subscriptions(
                     &apdu[0], apdu_max)) < 0) {
                rpdata->error_code =
                    ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
                apdu_len = BACNET_STATUS_ABORT;
            }
            break;
#endif
        case PROP_SERIAL_NUMBER:
            characterstring_init_ansi(&char_string, Serial_Number);
            apdu_len =
                encode_application_character_string(&apdu[0], &char_string);
            break;
        case PROP_TIME_OF_DEVICE_RESTART:
            apdu_len =
                bacapp_encode_timestamp(&apdu[0], &Time_Of_Device_Restart);
            break;
        default:
            rpdata->error_class = ERROR_CLASS_PROPERTY;
            rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            apdu_len = BACNET_STATUS_ERROR;
            break;
    }

    return apdu_len;
}

/** Looks up the common Object and Property, and encodes its Value in an
 * APDU. Sets the error class and code if request is not appropriate.
 * @param pObject - object table
 * @param rpdata [in,out] Structure with the requested Object & Property info
 *  on entry, and APDU message on return.
 * @return The length of the APDU on success, else BACNET_STATUS_ERROR
 */
static int Read_Property_Common(
    const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata)
{
    int apdu_len = BACNET_STATUS_ERROR;
    BACNET_CHARACTER_STRING char_string;
    uint8_t *apdu = NULL;
#if (BACNET_PROTOCOL_REVISION >= 14)
    struct special_property_list_t property_list;
#endif

    if ((rpdata->application_data == NULL) ||
        (rpdata->application_data_len == 0)) {
        return 0;
    }
    apdu = rpdata->application_data;
    if (property_list_common(rpdata->object_property)) {
        apdu_len = property_list_common_encode(rpdata, Object_Instance_Number);
    } else if (rpdata->object_property == PROP_OBJECT_NAME) {
        characterstring_init_ansi(&char_string, "");
        if (pObject->Object_Name) {
            (void)pObject->Object_Name(rpdata->object_instance, &char_string);
        }
        apdu_len = encode_application_character_string(&apdu[0], &char_string);
#if (BACNET_PROTOCOL_REVISION >= 14)
    } else if (rpdata->object_property == PROP_PROPERTY_LIST) {
        Device_Objects_Property_List(
            rpdata->object_type, rpdata->object_instance, &property_list);
        apdu_len = property_list_encode(
            rpdata, property_list.Required.pList, property_list.Optional.pList,
            property_list.Proprietary.pList);
#endif
    } else if (pObject->Object_Read_Property) {
        apdu_len = pObject->Object_Read_Property(rpdata);
    }

    return apdu_len;
}

/** Looks up the requested Object and Property, and encodes its Value in an
 * APDU.
 * @ingroup ObjIntf
 * If the Object or Property can't be found, sets the error class and code.
 *
 * @param rpdata [in,out] Structure with the desired Object and Property info
 *                 on entry, and APDU message on return.
 * @return The length of the APDU on success, else BACNET_STATUS_ERROR
 */
int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{
    int apdu_len = BACNET_STATUS_ERROR;
    struct object_functions *pObject = NULL;

    /* initialize the default return values */
    rpdata->error_class = ERROR_CLASS_OBJECT;
    rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    pObject = Device_Object_Functions_Find(rpdata->object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(rpdata->object_instance)) {
            apdu_len = Read_Property_Common(pObject, rpdata);
        } else {
            rpdata->error_class = ERROR_CLASS_OBJECT;
            rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
        }
    } else {
        rpdata->error_class = ERROR_CLASS_OBJECT;
        rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    }

    return apdu_len;
}

/* returns true if successful */
bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
    bool status = false; /* return value */
    int len = 0;
    BACNET_APPLICATION_DATA_VALUE value = { 0 };
    BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
    uint32_t object_instance = 0;
    int result = 0;
#if defined(BACNET_TIME_MASTER)
    uint32_t minutes = 0;
#endif

    /* decode the some of the request */
    len = bacapp_decode_known_property(
        wp_data->application_data, wp_data->application_data_len, &value,
        wp_data->object_type, wp_data->object_property);
    if (len < 0) {
        /* error while decoding - a value larger than we can handle */
        wp_data->error_class = ERROR_CLASS_PROPERTY;
        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
        return false;
    }
    /* FIXME: len < application_data_len: more data? */
    switch (wp_data->object_property) {
        case PROP_OBJECT_IDENTIFIER:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_OBJECT_ID);
            if (status) {
                if ((value.type.Object_Id.type == OBJECT_DEVICE) &&
                    (Device_Set_Object_Instance_Number(
                        value.type.Object_Id.instance))) {
                    /* we could send an I-Am broadcast to let the world know */
                } else {
                    status = false;
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
        case PROP_NUMBER_OF_APDU_RETRIES:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                /* FIXME: bounds check? */
                apdu_retries_set((uint8_t)value.type.Unsigned_Int);
            }
            break;
        case PROP_APDU_TIMEOUT:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                /* FIXME: bounds check? */
                apdu_timeout_set((uint16_t)value.type.Unsigned_Int);
            }
            break;
        case PROP_VENDOR_IDENTIFIER:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                /* FIXME: bounds check? */
                Device_Set_Vendor_Identifier((uint16_t)value.type.Unsigned_Int);
            }
            break;
        case PROP_SYSTEM_STATUS:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED);
            if (status) {
                result = Device_Set_System_Status(
                    (BACNET_DEVICE_STATUS)value.type.Enumerated, false);
                if (result != 0) {
                    /* result: - 0 = ok, -1 = bad value, -2 = not allowed */
                    status = false;
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    if (result == -1) {
                        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                    } else {
                        wp_data->error_code =
                            ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
                    }
                }
            }
            break;
        case PROP_OBJECT_NAME:
            status = write_property_string_valid(
                wp_data, &value, characterstring_capacity(&My_Object_Name));
            if (status) {
                /* All the object names in a device must be unique */
                if (Device_Valid_Object_Name(
                        &value.type.Character_String, &object_type,
                        &object_instance)) {
                    if ((object_type == wp_data->object_type) &&
                        (object_instance == wp_data->object_instance)) {
                        /* writing same name to same object */
                        status = true;
                    } else {
                        status = false;
                        wp_data->error_class = ERROR_CLASS_PROPERTY;
                        wp_data->error_code = ERROR_CODE_DUPLICATE_NAME;
                    }
                } else {
                    Device_Set_Object_Name(&value.type.Character_String);
                }
            }
            break;
        case PROP_LOCATION:
            status = write_property_empty_string_valid(
                wp_data, &value, MAX_DEV_LOC_LEN);
            if (status) {
                Device_Set_Location(
                    characterstring_value(&value.type.Character_String),
                    characterstring_length(&value.type.Character_String));
            }
            break;

        case PROP_DESCRIPTION:
            status = write_property_empty_string_valid(
                wp_data, &value, MAX_DEV_DESC_LEN);
            if (status) {
                Device_Set_Description(
                    characterstring_value(&value.type.Character_String),
                    characterstring_length(&value.type.Character_String));
            }
            break;
        case PROP_MODEL_NAME:
            status = write_property_empty_string_valid(
                wp_data, &value, MAX_DEV_MOD_LEN);
            if (status) {
                Device_Set_Model_Name(
                    characterstring_value(&value.type.Character_String),
                    characterstring_length(&value.type.Character_String));
            }
            break;
#if defined(BACNET_TIME_MASTER)
        case PROP_TIME_SYNCHRONIZATION_INTERVAL:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                if (value.type.Unsigned_Int < 65535) {
                    minutes = value.type.Unsigned_Int;
                    Device_Time_Sync_Interval_Set(minutes);
                    status = true;
                } else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
        case PROP_ALIGN_INTERVALS:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
            if (status) {
                Device_Align_Intervals_Set(value.type.Boolean);
                status = true;
            }
            break;
        case PROP_INTERVAL_OFFSET:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                if (value.type.Unsigned_Int < 65535) {
                    minutes = value.type.Unsigned_Int;
                    Device_Interval_Offset_Set(minutes);
                    status = true;
                } else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
#endif
        case PROP_UTC_OFFSET:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_SIGNED_INT);
            if (status) {
                if ((value.type.Signed_Int < (12 * 60)) &&
                    (value.type.Signed_Int > (-12 * 60))) {
                    Device_UTC_Offset_Set(value.type.Signed_Int);
                    status = true;
                } else {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
#if defined(BACDL_MSTP)
        case PROP_MAX_INFO_FRAMES:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                if (value.type.Unsigned_Int <= 255) {
                    dlmstp_set_max_info_frames(
                        (uint8_t)value.type.Unsigned_Int);
                } else {
                    status = false;
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
        case PROP_MAX_MASTER:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
            if (status) {
                if ((value.type.Unsigned_Int > 0) &&
                    (value.type.Unsigned_Int <= 127)) {
                    dlmstp_set_max_master((uint8_t)value.type.Unsigned_Int);
                } else {
                    status = false;
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
                }
            }
            break;
#endif
        case PROP_TIME_OF_DEVICE_RESTART:
            status = write_property_type_valid(
                wp_data, &value, BACNET_APPLICATION_TAG_TIMESTAMP);
            if (status) {
#if defined(BACAPP_TIMESTAMP)
                bacapp_timestamp_copy(
                    &Time_Of_Device_Restart, &value.type.Time_Stamp);
#endif
            }
            break;
        default:
            if (property_lists_member(
                    Device_Properties_Required, Device_Properties_Optional,
                    Device_Properties_Proprietary, wp_data->object_property)) {
                wp_data->error_class = ERROR_CLASS_PROPERTY;
                wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
            } else {
                wp_data->error_class = ERROR_CLASS_PROPERTY;
                wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            }
            break;
    }

    return status;
}

/**
 * @brief Handles the writing of the object name property
 * @param wp_data [in,out] WriteProperty data structure
 * @param Object_Write_Property object specific function to write the property
 * @return True on success, else False if there is an error.
 */
static bool Device_Write_Property_Object_Name(
    BACNET_WRITE_PROPERTY_DATA *wp_data,
    write_property_function Object_Write_Property)
{
    bool status = false; /* return value */
    int len = 0;
    BACNET_CHARACTER_STRING value;
    BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
    uint32_t object_instance = 0;
    int apdu_size = 0;
    const uint8_t *apdu = NULL;

    if (!wp_data) {
        return false;
    }
    apdu = wp_data->application_data;
    apdu_size = wp_data->application_data_len;
    len = bacnet_character_string_application_decode(apdu, apdu_size, &value);
    if (len > 0) {
        if ((characterstring_encoding(&value) != CHARACTER_ANSI_X34) ||
            (characterstring_length(&value) == 0) ||
            (!characterstring_printable(&value))) {
            wp_data->error_class = ERROR_CLASS_PROPERTY;
            wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
        } else {
            status = true;
        }
    } else if (len == 0) {
        wp_data->error_class = ERROR_CLASS_PROPERTY;
        wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
    } else {
        wp_data->error_class = ERROR_CLASS_PROPERTY;
        wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
    }
    if (status) {
        /* All the object names in a device must be unique */
        if (Device_Valid_Object_Name(&value, &object_type, &object_instance)) {
            if ((object_type == wp_data->object_type) &&
                (object_instance == wp_data->object_instance)) {
                /* writing same name to same object - but is it writable? */
                status = Object_Write_Property(wp_data);
            } else {
                /* name already exists in some object */
                wp_data->error_class = ERROR_CLASS_PROPERTY;
                wp_data->error_code = ERROR_CODE_DUPLICATE_NAME;
                status = false;
            }
        } else {
            status = Object_Write_Property(wp_data);
        }
    }

    return status;
}

/**
 * @brief Set the callback for a WriteProperty successful operation
 * @param cb [in] The function to be called, or NULL to disable
 */
void Device_Write_Property_Store_Callback_Set(write_property_function cb)
{
    Device_Write_Property_Store_Callback = cb;
}

/**
 * @brief Store the value of a property when WriteProperty is successful
 */
static void Device_Write_Property_Store(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
    if (Device_Write_Property_Store_Callback) {
        (void)Device_Write_Property_Store_Callback(wp_data);
    }
}

/** Looks up the requested Object and Property, and set the new Value in it,
 *  if allowed.
 * If the Object or Property can't be found, sets the error class and code.
 * @ingroup ObjIntf
 *
 * @param wp_data [in,out] Structure with the desired Object and Property info
 *              and new Value on entry, and APDU message on return.
 * @return True on success, else False if there is an error.
 */
bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
    bool status = false; /* Ever the pessimist! */
    struct object_functions *pObject = NULL;

    /* initialize the default return values */
    wp_data->error_class = ERROR_CLASS_OBJECT;
    wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    pObject = Device_Object_Functions_Find(wp_data->object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(wp_data->object_instance)) {
            if (pObject->Object_Write_Property) {
#if (BACNET_PROTOCOL_REVISION >= 14)
                if (wp_data->object_property == PROP_PROPERTY_LIST) {
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
                    return false;
                }
#endif
                if (wp_data->object_property == PROP_OBJECT_NAME) {
                    status = Device_Write_Property_Object_Name(
                        wp_data, pObject->Object_Write_Property);
                } else {
                    status = pObject->Object_Write_Property(wp_data);
                }
                if (status) {
                    Device_Write_Property_Store(wp_data);
                }
            } else {
                if (Device_Objects_Property_List_Member(
                        wp_data->object_type, wp_data->object_instance,
                        wp_data->object_property)) {
                    /* this property is not writable */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
                } else {
                    /* this property is not supported */
                    wp_data->error_class = ERROR_CLASS_PROPERTY;
                    wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
                }
            }
        } else {
            wp_data->error_class = ERROR_CLASS_OBJECT;
            wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
        }
    } else {
        wp_data->error_class = ERROR_CLASS_OBJECT;
        wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    }

    return (status);
}

/**
 * @brief AddListElement from an object list property
 * @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
 * which is packed with the information from the request.
 * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or
 * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT.
 */
int Device_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
{
    int status = BACNET_STATUS_ERROR;
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(list_element->object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(list_element->object_instance)) {
            if (pObject->Object_Add_List_Element) {
                status = pObject->Object_Add_List_Element(list_element);
            } else {
                list_element->error_class = ERROR_CLASS_PROPERTY;
                list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
            }
        } else {
            list_element->error_class = ERROR_CLASS_OBJECT;
            list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
        }
    } else {
        list_element->error_class = ERROR_CLASS_OBJECT;
        list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    }

    return status;
}

/**
 * @brief RemoveListElement from an object list property
 * @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
 * which is packed with the information from the request.
 * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or
 * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT.
 */
int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
{
    int status = BACNET_STATUS_ERROR;
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(list_element->object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(list_element->object_instance)) {
            if (pObject->Object_Remove_List_Element) {
                status = pObject->Object_Remove_List_Element(list_element);
            } else {
                list_element->error_class = ERROR_CLASS_PROPERTY;
                list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
            }
        } else {
            list_element->error_class = ERROR_CLASS_OBJECT;
            list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
        }
    } else {
        list_element->error_class = ERROR_CLASS_OBJECT;
        list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT;
    }

    return status;
}

/** Looks up the requested Object, and fills the Property Value list.
 * If the Object or Property can't be found, returns false.
 * @ingroup ObjHelpers
 * @param [in] The object type to be looked up.
 * @param [in] The object instance number to be looked up.
 * @param [out] The value list
 * @return True if the object instance supports this feature
 *         and was encoded correctly
 */
bool Device_Encode_Value_List(
    BACNET_OBJECT_TYPE object_type,
    uint32_t object_instance,
    BACNET_PROPERTY_VALUE *value_list)
{
    bool status = false; /* Ever the pessimist! */
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(object_instance)) {
            if (pObject->Object_Value_List) {
                status =
                    pObject->Object_Value_List(object_instance, value_list);
            }
        }
    }

    return (status);
}

/** Checks the COV flag in the requested Object
 * @ingroup ObjHelpers
 * @param [in] The object type to be looked up.
 * @param [in] The object instance to be looked up.
 * @return True if the COV flag is set
 */
bool Device_COV(BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
    bool status = false; /* Ever the pessamist! */
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(object_instance)) {
            if (pObject->Object_COV) {
                status = pObject->Object_COV(object_instance);
            }
        }
    }

    return (status);
}

/** Clears the COV flag in the requested Object
 * @ingroup ObjHelpers
 * @param [in] The object type to be looked up.
 * @param [in] The object instance to be looked up.
 */
void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
{
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(object_type);
    if (pObject != NULL) {
        if (pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(object_instance)) {
            if (pObject->Object_COV_Clear) {
                pObject->Object_COV_Clear(object_instance);
            }
        }
    }
}

/**
 * @brief Creates a child object, if supported
 * @ingroup ObjHelpers
 * @param data - CreateObject data, including error codes if failures
 * @return true if object has been created
 */
bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data)
{
    bool status = false;
    struct object_functions *pObject = NULL;
    uint32_t object_instance;

    pObject = Device_Object_Functions_Find(data->object_type);
    if (pObject != NULL) {
        if (!pObject->Object_Create) {
            /*  The device supports the object type and may have
                sufficient space, but does not support the creation of the
                object for some other reason.*/
            data->error_class = ERROR_CLASS_OBJECT;
            data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
        } else if (
            pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(data->object_instance)) {
            /* The object being created already exists */
            data->error_class = ERROR_CLASS_OBJECT;
            data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
        } else {
            if (data->list_of_initial_values) {
                /* FIXME: add support for writing to list of initial values */
                /*  A property specified by the Property_Identifier in the
                    List of Initial Values does not support initialization
                    during the CreateObject service. */
                data->first_failed_element_number = 1;
                data->error_class = ERROR_CLASS_PROPERTY;
                data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
                /* and the object shall not be created */
            } else {
                object_instance = pObject->Object_Create(data->object_instance);
                if (object_instance == BACNET_MAX_INSTANCE) {
                    /* The device cannot allocate the space needed
                    for the new object.*/
                    data->error_class = ERROR_CLASS_RESOURCES;
                    data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
                } else {
                    /* required by ACK */
                    data->object_instance = object_instance;
                    Device_Inc_Database_Revision();
                    status = true;
                }
            }
        }
    } else {
        /* The device does not support the specified object type. */
        data->error_class = ERROR_CLASS_OBJECT;
        data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
    }

    return status;
}

/**
 * @brief Deletes a child object, if supported
 * @ingroup ObjHelpers
 * @param data - DeleteObject data, including error codes if failures
 * @return true if object has been deleted
 */
bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data)
{
    bool status = false;
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(data->object_type);
    if (pObject != NULL) {
        if (!pObject->Object_Delete) {
            /*  The device supports the object type
                but does not support the deletion of the
                object for some reason.*/
            data->error_class = ERROR_CLASS_OBJECT;
            data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED;
        } else if (
            pObject->Object_Valid_Instance &&
            pObject->Object_Valid_Instance(data->object_instance)) {
            /* The object being deleted must already exist */
            status = pObject->Object_Delete(data->object_instance);
            if (status) {
                Device_Inc_Database_Revision();
            } else {
                /* The object exists but cannot be deleted. */
                data->error_class = ERROR_CLASS_OBJECT;
                data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED;
            }
        } else {
            /* The object to be deleted does not exist. */
            data->error_class = ERROR_CLASS_OBJECT;
            data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
        }
    } else {
        /* The device does not support the specified object type. */
        data->error_class = ERROR_CLASS_OBJECT;
        data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
    }

    return status;
}

/** Looks up the requested Object to see if the functionality is supported.
 * @ingroup ObjHelpers
 * @param [in] The object type to be looked up.
 * @return True if the object instance supports this feature.
 */
bool Device_Value_List_Supported(BACNET_OBJECT_TYPE object_type)
{
    bool status = false; /* Ever the pessamist! */
    struct object_functions *pObject = NULL;

    pObject = Device_Object_Functions_Find(object_type);
    if (pObject != NULL) {
        if (pObject->Object_Value_List) {
            status = true;
        }
    }

    return (status);
}

/**
 * @brief Get the Device object functions table
 * @return the Device object function table
 */
struct object_functions *Device_Object_Functions(void)
{
    return Object_Table;
}

/** Initialize the Device Object.
 Initialize the group of object helper functions for any supported Object.
 Initialize each of the Device Object child Object instances.
 * @ingroup ObjIntf
 * @param object_table [in,out] array of structure with object functions.
 *  Each Child Object must provide some implementation of each of these
 *  functions in order to properly support the default handlers.
 */
void Device_Init(object_functions_t *object_table)
{
    struct object_functions *pObject = NULL;

    datetime_init();
    if (object_table) {
        Object_Table = object_table;
    } else {
        Object_Table = &My_Object_Table[0];
    }
    pObject = Object_Table;
    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
        if (pObject->Object_Init) {
            pObject->Object_Init();
        }
        pObject++;
    }
    dcc_set_status_duration(COMMUNICATION_ENABLE, 0);
    if (Object_Instance_Number > BACNET_MAX_INSTANCE) {
        Object_Instance_Number = BACNET_MAX_INSTANCE;
    }
    characterstring_init_ansi(&My_Object_Name, Device_Name_Default);
#if (BACNET_PROTOCOL_REVISION >= 14)
#ifdef CONFIG_BACNET_BASIC_OBJECT_CHANNEL
    Channel_Write_Property_Internal_Callback_Set(Device_Write_Property);
#endif
#endif
}

bool DeviceGetRRInfo(
    BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */
    RR_PROP_INFO *pInfo)
{ /* Where to put the response */
    bool status = false; /* return value */

    switch (pRequest->object_property) {
        case PROP_VT_CLASSES_SUPPORTED:
        case PROP_ACTIVE_VT_SESSIONS:
        case PROP_LIST_OF_SESSION_KEYS:
        case PROP_TIME_SYNCHRONIZATION_RECIPIENTS:
        case PROP_MANUAL_SLAVE_ADDRESS_BINDING:
        case PROP_SLAVE_ADDRESS_BINDING:
        case PROP_RESTART_NOTIFICATION_RECIPIENTS:
        case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS:
            pInfo->RequestTypes = RR_BY_POSITION;
            pRequest->error_class = ERROR_CLASS_PROPERTY;
            if (pRequest->array_index == BACNET_ARRAY_ALL) {
                pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            } else {
                pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
            }
            break;

        case PROP_DEVICE_ADDRESS_BINDING:
            pInfo->RequestTypes = RR_BY_POSITION;
            pInfo->Handler = rr_address_list_encode;
            status = true;
            break;

        case PROP_ACTIVE_COV_SUBSCRIPTIONS:
            pInfo->RequestTypes = RR_BY_POSITION;
            pRequest->error_class = ERROR_CLASS_PROPERTY;
            if (pRequest->array_index == BACNET_ARRAY_ALL) {
                pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
            } else {
                pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
            }
            break;
        default:
            pRequest->error_class = ERROR_CLASS_SERVICES;
            pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
            if (pRequest->array_index == BACNET_ARRAY_ALL) {
                pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
                pRequest->error_class = ERROR_CLASS_PROPERTY;
            }
            break;
    }

    return status;
}

/**
 * @brief Updates all the object timers with elapsed milliseconds
 * @param milliseconds - number of milliseconds elapsed
 */
void Device_Timer(uint16_t milliseconds)
{
    struct object_functions *pObject;
    unsigned count = 0;
    uint32_t instance;

    pObject = Object_Table;
    while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) {
        if (pObject->Object_Count) {
            count = pObject->Object_Count();
        }
        while (count) {
            count--;
            if ((pObject->Object_Timer) &&
                (pObject->Object_Index_To_Instance)) {
                instance = pObject->Object_Index_To_Instance(count);
                pObject->Object_Timer(instance, milliseconds);
            }
        }
        pObject++;
    }
}
