/** * @file * @brief Base "class" for handling all BACnet objects belonging * to a BACnet device, as well as Device-specific properties. * @author Steve Karg * @date March 2024 * @copyright SPDX-License-Identifier: MIT */ #include #include #include #include /* 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. */ static struct object_functions * Device_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions((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_Objects_Find_Functions((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_Objects_Find_Functions(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_Objects_Find_Functions(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) { bacapp_timestamp_copy( &Time_Of_Device_Restart, &value.type.Time_Stamp); } 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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(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_Objects_Find_Functions(object_type); if (pObject != NULL) { if (pObject->Object_Value_List) { status = true; } } return (status); } /** 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) Channel_Write_Property_Internal_Callback_Set(Device_Write_Property); #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++; } }