diff --git a/CMakeLists.txt b/CMakeLists.txt index e3f85319..1247a0dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/piv.h src/bacnet/basic/object/schedule.c src/bacnet/basic/object/schedule.h + src/bacnet/basic/object/structured_view.c + src/bacnet/basic/object/structured_view.h src/bacnet/basic/object/time_value.c src/bacnet/basic/object/time_value.h src/bacnet/basic/object/trendlog.c diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index 0353d601..6074189c 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -40,6 +40,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/time_value.c \ $(BACNET_OBJECT_DIR)/trendlog.c \ $(BACNET_OBJECT_DIR)/schedule.c \ + $(BACNET_OBJECT_DIR)/structured_view.c \ $(BACNET_OBJECT_DIR)/access_credential.c \ $(BACNET_OBJECT_DIR)/access_door.c \ $(BACNET_OBJECT_DIR)/access_point.c \ diff --git a/apps/server/Makefile b/apps/server/Makefile index 83026c6f..0961c7b0 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -34,6 +34,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/time_value.c \ $(BACNET_OBJECT_DIR)/trendlog.c \ $(BACNET_OBJECT_DIR)/schedule.c \ + $(BACNET_OBJECT_DIR)/structured_view.c \ $(BACNET_OBJECT_DIR)/access_credential.c \ $(BACNET_OBJECT_DIR)/access_door.c \ $(BACNET_OBJECT_DIR)/access_point.c \ diff --git a/apps/server/main.c b/apps/server/main.c index df5c7ce2..42e091a5 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -31,12 +31,13 @@ /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ -#include "bacnet/bacdcode.h" #include "bacnet/apdu.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bactext.h" #include "bacnet/dcc.h" +#include "bacnet/getevent.h" #include "bacnet/iam.h" #include "bacnet/npdu.h" -#include "bacnet/getevent.h" #include "bacnet/version.h" /* some demo stuff needed */ #include "bacnet/basic/binding/address.h" @@ -58,6 +59,7 @@ #endif #include "bacnet/basic/object/lc.h" #include "bacnet/basic/object/trendlog.h" +#include "bacnet/basic/object/structured_view.h" #if defined(INTRINSIC_REPORTING) #include "bacnet/basic/object/nc.h" #endif /* defined(INTRINSIC_REPORTING) */ @@ -92,12 +94,103 @@ static struct mstimer BACnet_Object_Timer; /** Buffer used for receiving */ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +/* configure an example structured view object subordinate list */ +#if (BACNET_PROTOCOL_REVISION >= 4) +#define LIGHTING_OBJECT_WATTS OBJECT_ACCUMULATOR +#else +#define LIGHTING_OBJECT_WATTS OBJECT_ANALOG_INPUT +#endif +#if (BACNET_PROTOCOL_REVISION >= 6) +#define LIGHTING_OBJECT_ADR OBJECT_LOAD_CONTROL +#else +#define LIGHTING_OBJECT_ADR OBJECT_MULTISTATE_OUTPUT +#endif +#if (BACNET_PROTOCOL_REVISION >= 6) +#define LIGHTING_OBJECT_ADR OBJECT_LOAD_CONTROL +#else +#define LIGHTING_OBJECT_ADR OBJECT_MULTISTATE_OUTPUT +#endif +#if (BACNET_PROTOCOL_REVISION >= 14) +#define LIGHTING_OBJECT_SCENE OBJECT_CHANNEL +#define LIGHTING_OBJECT_LIGHT OBJECT_LIGHTING_OUTPUT +#else +#define LIGHTING_OBJECT_SCENE OBJECT_ANALOG_VALUE +#define LIGHTING_OBJECT_LIGHT OBJECT_ANALOG_OUTPUT +#endif +#if (BACNET_PROTOCOL_REVISION >= 16) +#define LIGHTING_OBJECT_RELAY OBJECT_BINARY_LIGHTING_OUTPUT +#else +#define LIGHTING_OBJECT_RELAY OBJECT_BINARY_OUTPUT +#endif + +static BACNET_SUBORDINATE_DATA Lighting_Subordinate[] = +{ + {0, LIGHTING_OBJECT_WATTS, 1, "watt-hours", 0, 0, NULL}, + {0, LIGHTING_OBJECT_ADR, 1, "demand-response", 0, 0, NULL}, + {0, LIGHTING_OBJECT_SCENE, 1, "scene", 0, 0, NULL}, + {0, LIGHTING_OBJECT_LIGHT, 1, "light", 0, 0, NULL}, + {0, LIGHTING_OBJECT_RELAY, 1, "relay", 0, 0, NULL}, +#if (BACNET_PROTOCOL_REVISION >= 24) + {0, OBJECT_COLOR, 1, "color", 0, 0, NULL}, + {0, OBJECT_COLOR_TEMPERATURE, 1, "color-temperature", 0, 0, NULL}, +#endif +}; + +/** + * @brief Update the strcutured view static data with device ID and linked lists + * @param device_id Device Instance to assign to every subordinate + */ +static void Structured_View_Update(void) +{ + uint32_t device_id, instance; + BACNET_DEVICE_OBJECT_REFERENCE represents = { 0 }; + size_t i; + + device_id = Device_Object_Instance_Number(); + for (i = 0; i < ARRAY_SIZE(Lighting_Subordinate); i++) { + /* link the lists */ + if (i < (ARRAY_SIZE(Lighting_Subordinate)-1)) { + Lighting_Subordinate[i].next = &Lighting_Subordinate[i+1]; + } + /* update the device instance to internal */ + Lighting_Subordinate[i].Device_Instance = device_id; + /* update the common node data */ + Lighting_Subordinate[i].Node_Type = BACNET_NODE_ROOM; + Lighting_Subordinate[i].Relationship = BACNET_RELATIONSHIP_CONTAINS; + } + instance = Structured_View_Index_To_Instance(0); + Structured_View_Subordinate_List_Set(instance, Lighting_Subordinate); + /* In some cases, the Structure View object will abstractly represent + this entity by itself, and this property will either be absent, + unconfigured, or point to itself. */ + represents.deviceIdentifier.type = OBJECT_NONE; + represents.deviceIdentifier.instance = BACNET_MAX_INSTANCE; + represents.objectIdentifier.type = OBJECT_DEVICE; + represents.objectIdentifier.instance = Device_Object_Instance_Number(); + Structured_View_Represents_Set(instance, &represents); + Structured_View_Node_Type_Set(instance, BACNET_NODE_ROOM); +} + /** Initialize the handlers we will utilize. * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler */ static void Init_Service_Handlers(void) { + BACNET_CREATE_OBJECT_DATA object_data = { 0 }; + unsigned int i = 0; + Device_Init(NULL); + /* create some dynamically created objects as examples */ + object_data.object_instance = BACNET_MAX_INSTANCE; + for (i = 0; i <= BACNET_OBJECT_TYPE_LAST; i++) { + object_data.object_type = i; + if (Device_Create_Object(&object_data)) { + printf("Created object %s-%u\n", bactext_object_type_name(i), + (unsigned)object_data.object_instance); + } + } + /* update structured view with this device instance */ + Structured_View_Update(); /* we need to handle who-is to support dynamic device binding */ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); @@ -296,7 +389,6 @@ int main(int argc, char *argv[]) if (Device_Object_Name(Device_Object_Instance_Number(), &DeviceName)) { printf("BACnet Device Name: %s\n", DeviceName.value); } - dlenv_init(); atexit(datalink_cleanup); /* broadcast an I-Am on startup */ diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj index cbfd6024..fc113762 100644 --- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj +++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj @@ -198,6 +198,7 @@ + diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters index ddce119b..8ac932a5 100644 --- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters +++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Object_Definitions/BACnet_Object_Definitions.vcxproj.filters @@ -84,6 +84,9 @@ Source Files + + Source Files + Source Files diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj index c49d419e..176ceef3 100644 --- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj +++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj @@ -76,6 +76,7 @@ + diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters index a2069cfd..40787be3 100644 --- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters +++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters @@ -384,6 +384,9 @@ Source Files\src\bacnet\basic\object + + Source Files\src\bacnet\basic\object + Source Files\src\bacnet\basic\object diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 64f307b6..9cff15b5 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -2048,9 +2048,58 @@ typedef enum BACnetNodeType { BACNET_NODE_COLLECTION = 8, BACNET_NODE_PROPERTY = 9, BACNET_NODE_FUNCTIONAL = 10, - BACNET_NODE_OTHER = 11 + BACNET_NODE_OTHER = 11, + BACNET_NODE_SUBSYSTEM = 12, + BACNET_NODE_BUILDING = 13, + BACNET_NODE_FLOOR = 14, + BACNET_NODE_SECTION = 15, + BACNET_NODE_MODULE = 16, + BACNET_NODE_TREE = 17, + BACNET_NODE_MEMBER = 18, + BACNET_NODE_PROTOCOL = 19, + BACNET_NODE_ROOM = 20, + BACNET_NODE_ZONE = 21 } BACNET_NODE_TYPE; +typedef enum BACnetRelationship { + BACNET_RELATIONSHIP_UNKNOWN = 0, + BACNET_RELATIONSHIP_DEFAULT = 1, + BACNET_RELATIONSHIP_CONTAINS = 2, + BACNET_RELATIONSHIP_CONTAINED_BY = 3, + BACNET_RELATIONSHIP_USES = 4, + BACNET_RELATIONSHIP_USED_BY = 5, + BACNET_RELATIONSHIP_COMMANDS = 6, + BACNET_RELATIONSHIP_COMMANDED_BY = 7, + BACNET_RELATIONSHIP_ADJUSTS = 8, + BACNET_RELATIONSHIP_ADJUSTED_BY = 9, + BACNET_RELATIONSHIP_INGRESS = 10, + BACNET_RELATIONSHIP_EGRESS = 11, + BACNET_RELATIONSHIP_SUPPLIES_AIR = 12, + BACNET_RELATIONSHIP_RECEIVES_AIR = 13, + BACNET_RELATIONSHIP_SUPPLIES_HOT_AIR = 14, + BACNET_RELATIONSHIP_RECEIVES_HOT_AIR = 15, + BACNET_RELATIONSHIP_SUPPLIES_COOL_AIR = 16, + BACNET_RELATIONSHIP_RECEIVES_COOL_AIR = 17, + BACNET_RELATIONSHIP_SUPPLIES_POWER = 18, + BACNET_RELATIONSHIP_RECEIVES_POWER = 19, + BACNET_RELATIONSHIP_SUPPLIES_GAS = 20, + BACNET_RELATIONSHIP_RECEIVES_GAS = 21, + BACNET_RELATIONSHIP_SUPPLIES_WATER = 22, + BACNET_RELATIONSHIP_RECEIVES_WATER = 23, + BACNET_RELATIONSHIP_SUPPLIES_HOT_WATER = 24, + BACNET_RELATIONSHIP_RECEIVES_HOT_WATER = 25, + BACNET_RELATIONSHIP_SUPPLIES_COOL_WATER = 26, + BACNET_RELATIONSHIP_RECEIVES_COOL_WATER = 27, + BACNET_RELATIONSHIP_SUPPLIES_STEAM = 28, + BACNET_RELATIONSHIP_RECEIVES_STEAM = 29, + BACNET_RELATIONSHIP_RESERVED_MAX = 1023, + BACNET_RELATIONSHIP_PROPRIETARY_MIN = 1024, + BACNET_RELATIONSHIP_PROPRIETARY_MAX = 65535 + /* Enumerated values 0-1023 are reserved for definition by ASHRAE. + Enumerated values 1024-65535 may be used by others subject + to the procedures and constraints described in Clause 23. */ +} BACNET_RELATIONSHIP; + typedef enum BACnetShedState { BACNET_SHED_INACTIVE = 0, BACNET_SHED_REQUEST_PENDING = 1, @@ -2107,8 +2156,8 @@ typedef enum BACnetBinaryLightingPV { BINARY_LIGHTING_PV_WARN_RELINQUISH = 4, BINARY_LIGHTING_PV_STOP = 5, BINARY_LIGHTING_PV_MAX = 6, - /* -- Enumerated values 0-63 are reserved for definition by ASHRAE. - -- Enumerated values 64-255 may be used by others + /* -- Enumerated values 0-63 are reserved for definition by ASHRAE. + -- Enumerated values 64-255 may be used by others -- subject to the procedures and constraints described in Clause 23. */ BINARY_LIGHTING_PV_PROPRIETARY_MIN = 64, BINARY_LIGHTING_PV_PROPRIETARY_MAX = 255 @@ -2315,8 +2364,8 @@ typedef enum BACnetAccessZoneOccupancyState { ACCESS_ZONE_OCCUPANCY_STATE_ABOVE_UPPER_LIMIT = 4, ACCESS_ZONE_OCCUPANCY_STATE_DISABLED = 5, ACCESS_ZONE_OCCUPANCY_STATE_NOT_SUPPORTED = 6, - /* Enumerated values 0-63 are reserved for definition by ASHRAE. - Enumerated values 64-65535 may be used by others subject + /* Enumerated values 0-63 are reserved for definition by ASHRAE. + Enumerated values 64-65535 may be used by others subject to the procedures and constraints described in Clause 23. */ ACCESS_ZONE_OCCUPANCY_STATE_PROPRIETARY_MIN = 64, ACCESS_ZONE_OCCUPANCY_STATE_PROPRIETARY_MAX = 65535 @@ -2624,18 +2673,18 @@ typedef enum BACnetEscalatorFault { ESCALATOR_FAULT_PROPRIETARY_MAX = 65535 } BACNET_ESCALATOR_FAULT; -typedef enum BACnetEscalatorMode { +typedef enum BACnetEscalatorMode { ESCALATOR_MODE_UNKNOWN = 0, - ESCALATOR_MODE_STOP = 1, - ESCALATOR_MODE_UP = 2, + ESCALATOR_MODE_STOP = 1, + ESCALATOR_MODE_UP = 2, ESCALATOR_MODE_DOWN = 3, - ESCALATOR_MODE_INSPECTION = 4, + ESCALATOR_MODE_INSPECTION = 4, ESCALATOR_MODE_OUT_OF_SERVICE = 5, /* Enumerated values 0-1023 are reserved for definition by ASHRAE. Enumerated values 1024-65535 may be used by others subject to the procedures and constraints described in Clause 23. */ ESCALATOR_MODE_PROPRIETARY_MIN = 1024, - ESCALATOR_MODE_PROPRIETARY_MAX = 65535 + ESCALATOR_MODE_PROPRIETARY_MAX = 65535 } BACNET_ESCALATOR_MODE; typedef enum BACnetEscalatorOperationDirection { @@ -2779,8 +2828,8 @@ typedef enum BACnetAuditOperation { /* Enumerated values 0-31 are reserved for definition by ASHRAE. Enumerated values 32-63 may be used by others subject to the procedures and constraints described in Clause 23. - - The enumerated values match the bit positions in + + The enumerated values match the bit positions in BACnetAuditOperationFlags. */ AUDIT_OPERATION_PROPRIETARY_MIN = 32, AUDIT_OPERATION_PROPRIETARY_MAX = 63 diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 8369fbef..e601b114 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -39,11 +39,14 @@ #include "bacnet/basic/object/bo.h" #include "bacnet/basic/object/bv.h" #include "bacnet/basic/object/calendar.h" -#include "bacnet/basic/object/channel.h" #include "bacnet/basic/object/command.h" #include "bacnet/basic/object/csv.h" #include "bacnet/basic/object/iv.h" #include "bacnet/basic/object/lc.h" +#if (BACNET_PROTOCOL_REVISION >= 14) +#include "bacnet/basic/object/channel.h" +#include "bacnet/basic/object/lo.h" +#endif #include "bacnet/basic/object/lsp.h" #include "bacnet/basic/object/lsz.h" #include "bacnet/basic/object/ms-input.h" @@ -55,6 +58,7 @@ #include "bacnet/basic/object/osv.h" #include "bacnet/basic/object/piv.h" #include "bacnet/basic/object/schedule.h" +#include "bacnet/basic/object/structured_view.h" #include "bacnet/basic/object/time_value.h" #include "bacnet/basic/object/trendlog.h" #if defined(INTRINSIC_REPORTING) @@ -78,6 +82,7 @@ extern bool Routed_Device_Write_Property_Local( /* may be overridden by outside table */ static object_functions_t *Object_Table; +/* clang format off */ static object_functions_t My_Object_Table[] = { { OBJECT_DEVICE, NULL /* Init - don't init Device or it will recourse! */, Device_Count, Device_Index_To_Instance, @@ -111,7 +116,7 @@ static object_functions_t My_Object_Table[] = { 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 */, + 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 */, @@ -138,7 +143,7 @@ static object_functions_t My_Object_Table[] = { 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 */, + 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 */, @@ -147,7 +152,7 @@ static object_functions_t My_Object_Table[] = { 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 */, + 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 */, @@ -227,7 +232,7 @@ static object_functions_t My_Object_Table[] = { 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 */, + 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 */, @@ -237,7 +242,7 @@ static object_functions_t My_Object_Table[] = { Multistate_Output_Valid_Instance, Multistate_Output_Object_Name, Multistate_Output_Read_Property, Multistate_Output_Write_Property, Multistate_Output_Property_Lists, NULL /* ReadRangeInfo */, - NULL /* Iterator */, + 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 */, @@ -343,6 +348,14 @@ static object_functions_t My_Object_Table[] = { NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, + { 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 */ }, { OBJECT_TIME_VALUE, Time_Value_Init, Time_Value_Count, Time_Value_Index_To_Instance, Time_Value_Valid_Instance, Time_Value_Object_Name, Time_Value_Read_Property, @@ -368,6 +381,7 @@ static object_functions_t My_Object_Table[] = { NULL /* Add_List_Element */, NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, }; +/* clang format on */ /** Glue function to let the Device object, when called by a handler, * lookup which Object type needs to be invoked. @@ -462,16 +476,19 @@ void Device_Objects_Property_List(BACNET_OBJECT_TYPE object_type, return; } +/* clang format off */ /* These three arrays are used by the ReadPropertyMultiple handler */ -static const int Device_Properties_Required[] = { 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_Required[] = { + 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[] = { #if defined(BACDL_MSTP) @@ -486,7 +503,10 @@ static const int Device_Properties_Optional[] = { -1 }; -static const int Device_Properties_Proprietary[] = { -1 }; +static const int Device_Properties_Proprietary[] = { + -1 +}; +/* clang format on */ void Device_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) @@ -2231,16 +2251,6 @@ void Device_Init(object_functions_t *object_table) } pObject++; } - /* create some dynamically created objects as examples */ - if (!object_table) { - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Create) { - pObject->Object_Create(BACNET_MAX_INSTANCE); - } - pObject++; - } - } #if (BACNET_PROTOCOL_REVISION >= 14) Channel_Write_Property_Internal_Callback_Set(Device_Write_Property); #endif diff --git a/src/bacnet/basic/object/structured_view.c b/src/bacnet/basic/object/structured_view.c new file mode 100644 index 00000000..b4112a6d --- /dev/null +++ b/src/bacnet/basic/object/structured_view.c @@ -0,0 +1,882 @@ +/** + * @file + * @author Steve Karg + * @date May 2024 + * @brief Structured View object is an object that provides a container + * to hold references to subordinate objects, which may include other + * Structured View objects, thereby allowing multilevel hierarchies + * to be created. + * + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacapp.h" +#include "bacnet/bactext.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/proplist.h" +#include "bacnet/reject.h" +#include "bacnet/rp.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "structured_view.h" + +struct object_data { + const char *Object_Name; + const char *Description; + BACNET_NODE_TYPE Node_Type; + const char *Node_Subtype; + BACNET_SUBORDINATE_DATA *Subordinate_List; + BACNET_RELATIONSHIP Default_Subordinate_Relationship; + BACNET_DEVICE_OBJECT_REFERENCE Represents; +}; + +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; + +/* clang-format off */ +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, + PROP_NODE_TYPE, PROP_SUBORDINATE_LIST, + -1 +}; + +static const int Properties_Optional[] = { + PROP_DESCRIPTION, PROP_NODE_SUBTYPE, PROP_SUBORDINATE_ANNOTATIONS, + PROP_SUBORDINATE_NODE_TYPES, PROP_SUBORDINATE_RELATIONSHIPS, + PROP_DEFAULT_SUBORDINATE_RELATIONSHIP, + PROP_REPRESENTS, + -1 +}; + +static const int Properties_Proprietary[] = { + -1 +}; + +/* standard properties that are arrays for this object, + but not necessary supported in this object */ +static const int BACnetARRAY_Properties[] = { + PROP_SUBORDINATE_LIST, PROP_SUBORDINATE_ANNOTATIONS, PROP_SUBORDINATE_TAGS, + PROP_SUBORDINATE_NODE_TYPES, PROP_SUBORDINATE_RELATIONSHIPS, PROP_TAGS, + -1 +}; +/* clang-format on */ + +/** + * Returns the list of required, optional, and proprietary properties. + * Used by ReadPropertyMultiple service. + * + * @param pRequired - pointer to list of int terminated by -1, of + * BACnet required properties for this object. + * @param pOptional - pointer to list of int terminated by -1, of + * BACnet optkional properties for this object. + * @param pProprietary - pointer to list of int terminated by -1, of + * BACnet proprietary properties for this object. + */ +void Structured_View_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Properties_Required; + } + if (pOptional) { + *pOptional = Properties_Optional; + } + if (pProprietary) { + *pProprietary = Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Structured View instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Structured_View_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + return true; + } + + return false; +} + +/** + * Determines the number of Structured View objects + * + * @return Number of Structured View objects + */ +unsigned Structured_View_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..(N-1) index + * of Structured View objects where N is Structured_View_Count(). + * + * @param index - 0..(N-1) where N is Structured_View_Count() + * + * @return object instance-number for the given index + */ +uint32_t Structured_View_Index_To_Instance(unsigned index) +{ + KEY key = UINT32_MAX; + + Keylist_Index_Key(Object_List, index, &key); + + return key; +} + +/** + * For a given object instance-number, determines a 0..(N-1) index + * of Structured View objects where N is Structured_View_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or Structured_View_Count() + * if not valid. + */ +unsigned Structured_View_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * For a given object instance-number, loads the object-name into + * a characterstring. Note that the object name must be unique + * within this device. + * + * @param object_instance - object-instance number of the object + * @param object_name - holds the object-name retrieved + * + * @return true if object-name was retrieved + */ +bool Structured_View_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[24] = "Structured-View-4194303"; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Object_Name) { + status = + characterstring_init_ansi(object_name, pObject->Object_Name); + } else { + snprintf( + name_text, sizeof(name_text), "Structured-View-%u", + object_instance); + status = characterstring_init_ansi(object_name, name_text); + } + } + + return status; +} + +/** + * For a given object instance-number, sets the object-name + * Note that the object name must be unique within this device. + * + * @param object_instance - object-instance number of the object + * @param new_name - holds the object-name to be set + * + * @return true if object-name was set + */ +bool Structured_View_Name_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + status = true; + pObject->Object_Name = new_name; + } + + return status; +} + +/** + * For a given object instance-number, returns the description + * + * @param object_instance - object-instance number of the object + * + * @return description text or NULL if not found + */ +char *Structured_View_Description(uint32_t object_instance) +{ + char *name = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Description) { + name = (char *)pObject->Description; + } else { + name = ""; + } + } + + return name; +} + +/** + * For a given object instance-number, sets the description + * + * @param object_instance - object-instance number of the object + * @param new_name - holds the description to be set + * + * @return true if object-name was set + */ +bool Structured_View_Description_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + pObject->Description = new_name; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Node_Type + * @param object_instance - object-instance number of the object + * @return Node_Type or BACNET_NODE_UNKNOWN if not found + */ +BACNET_NODE_TYPE Structured_View_Node_Type(uint32_t object_instance) +{ + BACNET_NODE_TYPE node_type = BACNET_NODE_UNKNOWN; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + node_type = pObject->Node_Type; + } + + return node_type; +} + +/** + * @brief For a given object instance-number, sets the Node_Type + * @param object_instance - object-instance number of the object + * @param node_type - holds the Node_Type to be set + * @return true if Node_Type was set + */ +bool Structured_View_Node_Type_Set( + uint32_t object_instance, BACNET_NODE_TYPE node_type) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + pObject->Node_Type = node_type; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Node_Subtype + * @param object_instance - object-instance number of the object + * @return Node_Subtype text or NULL if not found + */ +char *Structured_View_Node_Subtype(uint32_t object_instance) +{ + char *name = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Description) { + name = (char *)pObject->Node_Subtype; + } else { + name = ""; + } + } + + return name; +} + +/** + * @brief For a given object instance-number, sets the Node_Subtype + * @param object_instance - object-instance number of the object + * @param new_name - holds the Node_Subtype to be set + * @return true if Node_Subtype was set + */ +bool Structured_View_Node_Subtype_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + pObject->Description = new_name; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Subordinate_List + * @param object_instance - object-instance number of the object + * @return Subordinate_List or NULL if not found + */ +BACNET_SUBORDINATE_DATA * +Structured_View_Subordinate_List(uint32_t object_instance) +{ + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + subordinate_list = pObject->Subordinate_List; + } + + return subordinate_list; +} + +/** + * @brief For a given object instance-number, sets the Subordinate_List + * @param object_instance - object-instance number of the object + * @param subordinate_list - holds the Subordinate_List to be set + */ +void Structured_View_Subordinate_List_Set( + uint32_t object_instance, BACNET_SUBORDINATE_DATA *subordinate_list) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Subordinate_List = subordinate_list; + } +} + +/** + * @brief For a given object instance-number, returns the + * Default_Subordinate_Relationship + * @param object_instance - object-instance number of the object + * @return Default_Subordinate_Relationship or BACNET_RELATIONSHIP_DEFAULT if + * not found + */ +BACNET_RELATIONSHIP +Structured_View_Default_Subordinate_Relationship(uint32_t object_instance) +{ + BACNET_RELATIONSHIP relationship = BACNET_RELATIONSHIP_DEFAULT; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + relationship = pObject->Default_Subordinate_Relationship; + } + + return relationship; +} + +/** + * @brief For a given object instance-number, sets the + * Default_Subordinate_Relationship + * @param object_instance - object-instance number of the object + * @param relationship - holds the Default_Subordinate_Relationship to be set + * @return true if Default_Subordinate_Relationship was set + */ +bool Structured_View_Default_Subordinate_Relationship_Set( + uint32_t object_instance, BACNET_RELATIONSHIP relationship) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + pObject->Default_Subordinate_Relationship = relationship; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the Represents + * property value + * @param object_instance - object-instance number of the object + * @return Represents or NULL if not found + */ +BACNET_DEVICE_OBJECT_REFERENCE * +Structured_View_Represents(uint32_t object_instance) +{ + BACNET_DEVICE_OBJECT_REFERENCE *represents = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + represents = &pObject->Represents; + } + + return represents; +} + +/** + * @brief For a given object instance-number, sets the Represents + * property value + * @param object_instance - object-instance number of the object + * @param represents - holds the Represents to be set + * @return true if Represents was set + */ +bool Structured_View_Represents_Set( + uint32_t object_instance, BACNET_DEVICE_OBJECT_REFERENCE *represents) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + pObject->Represents = *represents; + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the number of + * Subordinate_List elements + * @param object_instance - object-instance number of the object + * @return number of Subordinate_List elements + */ +unsigned int Structured_View_Subordinate_List_Count(uint32_t object_instance) +{ + unsigned int count = 0; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + subordinate_list = pObject->Subordinate_List; + while (subordinate_list) { + count++; + subordinate_list = subordinate_list->next; + } + } + + return count; +} + +/** + * @brief For a given object instance-number, returns the number of + * Subordinate_List elements + * @param object_instance - object-instance number of the object + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @return Subordinate_List element or NULL if not found + */ +BACNET_SUBORDINATE_DATA *Structured_View_Subordinate_List_Member( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index) +{ + BACNET_ARRAY_INDEX index = 0; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + subordinate_list = pObject->Subordinate_List; + while (subordinate_list) { + if (index == array_index) { + break; + } + index++; + subordinate_list = subordinate_list->next; + } + } + + return subordinate_list; +} + +/** + * @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 Structured_View_Subordinate_List_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_DEVICE_OBJECT_REFERENCE value = { 0 }; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + + subordinate_list = + Structured_View_Subordinate_List_Member(object_instance, array_index); + if (subordinate_list) { + /* BACnetDeviceObjectReference */ + value.deviceIdentifier.type = OBJECT_DEVICE; + value.deviceIdentifier.instance = subordinate_list->Device_Instance; + value.objectIdentifier.type = subordinate_list->Object_Type; + value.objectIdentifier.instance = subordinate_list->Object_Instance; + apdu_len = bacapp_encode_device_obj_ref(apdu, &value); + } + + return apdu_len; +} + +/** + * @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 Structured_View_Subordinate_Annotations_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_CHARACTER_STRING value = { 0 }; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + + subordinate_list = + Structured_View_Subordinate_List_Member(object_instance, array_index); + if (subordinate_list) { + /* BACnetCharacterString */ + characterstring_init_ansi(&value, subordinate_list->Annotations); + apdu_len = encode_application_character_string(apdu, &value); + } + + return apdu_len; +} + +/** + * @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 Structured_View_Subordinate_Node_Types_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + + subordinate_list = + Structured_View_Subordinate_List_Member(object_instance, array_index); + if (subordinate_list) { + /* BACnetNodeType */ + apdu_len = + encode_application_enumerated(apdu, subordinate_list->Node_Type); + } + + return apdu_len; +} + +/** + * @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 Structured_View_Subordinate_Relationships_Element_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + + subordinate_list = + Structured_View_Subordinate_List_Member(object_instance, array_index); + if (subordinate_list) { + /* BACnetRelationship */ + apdu_len = + encode_application_enumerated(apdu, subordinate_list->Relationship); + } + + return apdu_len; +} + +/** + * @brief Determine if the object property is a BACnetARRAY property + * @param object_property - object-property to be checked + * @return true if the property is a BACnetARRAY property + */ +static bool BACnetARRAY_Property(int object_property) +{ + return property_list_member(BACnetARRAY_Properties, object_property); +} + +/** + * ReadProperty handler for this object. For the given ReadProperty + * data, the application_data is loaded or the error flags are set. + * + * @param rpdata - BACNET_READ_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return number of APDU bytes in the response, or + * BACNET_STATUS_ERROR on error. + */ +int Structured_View_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_CHARACTER_STRING char_string; + uint32_t count = 0; + uint8_t *apdu = 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 (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + break; + case PROP_OBJECT_NAME: + Structured_View_Object_Name(rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], rpdata->object_type); + break; + case PROP_NODE_TYPE: + apdu_len = encode_application_enumerated( + &apdu[0], Structured_View_Node_Type(rpdata->object_instance)); + break; + case PROP_SUBORDINATE_LIST: + count = + Structured_View_Subordinate_List_Count(rpdata->object_instance); + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Structured_View_Subordinate_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_DESCRIPTION: + characterstring_init_ansi( + &char_string, + Structured_View_Description(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + break; + case PROP_NODE_SUBTYPE: + characterstring_init_ansi( + &char_string, + Structured_View_Node_Subtype(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + break; + case PROP_SUBORDINATE_ANNOTATIONS: + count = + Structured_View_Subordinate_List_Count(rpdata->object_instance); + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Structured_View_Subordinate_Annotations_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_SUBORDINATE_NODE_TYPES: + count = + Structured_View_Subordinate_List_Count(rpdata->object_instance); + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Structured_View_Subordinate_Node_Types_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_SUBORDINATE_RELATIONSHIPS: + count = + Structured_View_Subordinate_List_Count(rpdata->object_instance); + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Structured_View_Subordinate_Relationships_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_DEFAULT_SUBORDINATE_RELATIONSHIP: + apdu_len = encode_application_enumerated( + &apdu[0], + Structured_View_Default_Subordinate_Relationship( + rpdata->object_instance)); + break; + case PROP_REPRESENTS: + apdu_len = bacapp_encode_device_obj_ref( + &apdu[0], Structured_View_Represents(rpdata->object_instance)); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (!BACnetARRAY_Property(rpdata->object_property)) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * Creates a Structured View object + * @param object_instance - object-instance number of the object + * @return object_instance if the object is created, else BACNET_MAX_INSTANCE + */ +uint32_t Structured_View_Create(uint32_t object_instance) +{ + struct object_data *pObject = NULL; + int index = 0; + + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + pObject = calloc(1, sizeof(struct object_data)); + if (!pObject) { + return BACNET_MAX_INSTANCE; + } + pObject->Object_Name = NULL; + pObject->Description = NULL; + pObject->Node_Subtype = NULL; + pObject->Subordinate_List = NULL; + pObject->Default_Subordinate_Relationship = BACNET_RELATIONSHIP_DEFAULT; + pObject->Represents.deviceIdentifier.type = OBJECT_NONE; + pObject->Represents.deviceIdentifier.instance = BACNET_MAX_INSTANCE; + pObject->Represents.objectIdentifier.type = OBJECT_DEVICE; + pObject->Represents.objectIdentifier.instance = BACNET_MAX_INSTANCE; + pObject->Node_Type = BACNET_NODE_UNKNOWN; + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index < 0) { + free(pObject); + return BACNET_MAX_INSTANCE; + } + } + + return object_instance; +} + +/** + * Deletes an Structured View object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Structured_View_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + free(pObject); + status = true; + } + + return status; +} + +/** + * Deletes all the Time Values and their data + */ +void Structured_View_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the Structured View object data + */ +void Structured_View_Init(void) +{ + Object_List = Keylist_Create(); + if (Object_List) { + atexit(Structured_View_Cleanup); + } +} diff --git a/src/bacnet/basic/object/structured_view.h b/src/bacnet/basic/object/structured_view.h new file mode 100644 index 00000000..8b48c53d --- /dev/null +++ b/src/bacnet/basic/object/structured_view.h @@ -0,0 +1,110 @@ +/** + * @file + * @author Steve Karg + * @date May 2024 + * @brief API for a Structured View object used by a BACnet device object + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_STRUCTURED_VIEW_OBJECT_H +#define BACNET_STRUCTURED_VIEW_OBJECT_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacerror.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacdevobjpropref.h" +#include "bacnet/rp.h" + +struct BACnetSubordinateData; +typedef struct BACnetSubordinateData { + uint32_t Device_Instance; + BACNET_OBJECT_TYPE Object_Type; + uint32_t Object_Instance; + const char *Annotations; + BACNET_NODE_TYPE Node_Type; + BACNET_RELATIONSHIP Relationship; + /* simple linked list */ + struct BACnetSubordinateData *next; +} BACNET_SUBORDINATE_DATA; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Structured_View_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Structured_View_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Structured_View_Count(void); +BACNET_STACK_EXPORT +uint32_t Structured_View_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Structured_View_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Structured_View_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Structured_View_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +int Structured_View_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +char *Structured_View_Description(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Description_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +BACNET_NODE_TYPE Structured_View_Node_Type(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Node_Type_Set( + uint32_t object_instance, BACNET_NODE_TYPE node_type); + +BACNET_STACK_EXPORT +char *Structured_View_Node_Subtype(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Node_Subtype_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +BACNET_SUBORDINATE_DATA * +Structured_View_Subordinate_List(uint32_t object_instance); +BACNET_STACK_EXPORT +void Structured_View_Subordinate_List_Set( + uint32_t object_instance, BACNET_SUBORDINATE_DATA *subordinate_list); + +BACNET_STACK_EXPORT +BACNET_RELATIONSHIP +Structured_View_Default_Subordinate_Relationship(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Default_Subordinate_Relationship_Set( + uint32_t object_instance, BACNET_RELATIONSHIP relationship); + +BACNET_STACK_EXPORT +BACNET_DEVICE_OBJECT_REFERENCE * +Structured_View_Represents(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Represents_Set( + uint32_t object_instance, BACNET_DEVICE_OBJECT_REFERENCE *represents); + +BACNET_STACK_EXPORT +uint32_t Structured_View_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Structured_View_Delete(uint32_t object_instance); + +BACNET_STACK_EXPORT +void Structured_View_Cleanup(void); +BACNET_STACK_EXPORT +void Structured_View_Init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cef431b8..f90e7166 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -140,6 +140,7 @@ list(APPEND testdirs bacnet/basic/object/osv bacnet/basic/object/piv bacnet/basic/object/schedule + bacnet/basic/object/structured_view bacnet/basic/object/time_value bacnet/basic/object/trendlog # basic/sys diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index e42d2e09..b4535ded 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -73,6 +73,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/osv.c ${SRC_DIR}/bacnet/basic/object/piv.c ${SRC_DIR}/bacnet/basic/object/schedule.c + ${SRC_DIR}/bacnet/basic/object/structured_view.c ${SRC_DIR}/bacnet/basic/object/time_value.c ${SRC_DIR}/bacnet/basic/object/trendlog.c ${SRC_DIR}/bacnet/basic/service/h_apdu.c diff --git a/test/bacnet/basic/object/property_test.c b/test/bacnet/basic/object/property_test.c index 4ad227e9..732ac7b3 100644 --- a/test/bacnet/basic/object/property_test.c +++ b/test/bacnet/basic/object/property_test.c @@ -221,6 +221,8 @@ void bacnet_object_properties_read_write_test( zassert_equal(len, BACNET_STATUS_ERROR, NULL); wpdata.object_property = PROP_ALL; wpdata.array_index = BACNET_ARRAY_ALL; - status = write_property(&wpdata); - zassert_false(status, NULL); + if (write_property) { + status = write_property(&wpdata); + zassert_false(status, NULL); + } } diff --git a/test/bacnet/basic/object/structured_view/CMakeLists.txt b/test/bacnet/basic/object/structured_view/CMakeLists.txt new file mode 100644 index 00000000..96712101 --- /dev/null +++ b/test/bacnet/basic/object/structured_view/CMakeLists.txt @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/bacnet/basic/object + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/object/structured_view.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/proplist.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/wp.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + ${SRC_DIR}/bacnet/calendar_entry.c + ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/debug.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + # Test and test library files + ./src/main.c + ${TST_DIR}/bacnet/basic/object/property_test.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/object/structured_view/src/main.c b/test/bacnet/basic/object/structured_view/src/main.c new file mode 100644 index 00000000..63805d5d --- /dev/null +++ b/test/bacnet/basic/object/structured_view/src/main.c @@ -0,0 +1,59 @@ +/** + * @file + * @brief Unit test for object + * @author Steve Karg + * @date May 2024 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(tests_object_structured_view, test_object_structured_view) +#else +static void test_object_structured_view(void) +#endif +{ + bool status = false; + unsigned count = 0, index = 0; + const uint32_t instance = 123; + const int skip_fail_property_list[] = { -1 }; + + Structured_View_Init(); + Structured_View_Create(instance); + status = Structured_View_Valid_Instance(instance); + zassert_true(status, NULL); + index = Structured_View_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + count = Structured_View_Count(); + zassert_true(count > 0, NULL); + bacnet_object_properties_read_write_test( + OBJECT_STRUCTURED_VIEW, instance, Structured_View_Property_Lists, + Structured_View_Read_Property, NULL, skip_fail_property_list); +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(tests_object_structured_view, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite( + tests_object_structured_view, + ztest_unit_test(test_object_structured_view)); + + ztest_run_test_suite(tests_object_structured_view); +} +#endif diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 75b7298e..90f0b330 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -372,6 +372,7 @@ set(BACNETSTACK_BASIC_SRCS $<$:${BACNETSTACK_SRC}/bacnet/basic/object/osv.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/piv.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/schedule.c> + $<$:${BACNETSTACK_SRC}/bacnet/basic/object/structured_view.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/time_value.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.c> ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.c diff --git a/zephyr/Kconfig b/zephyr/Kconfig index c4aa28db..a3ea4a0e 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -342,6 +342,12 @@ config BACNET_BASIC_OBJECT_SCHEDULE help Use the BACnet basic schedule object +config BACNET_BASIC_OBJECT_STRUCTURED_VIEW + bool "Use the BACnet basic structured view object" + default false + help + Use the BACnet basic structured view object + config BACNET_BASIC_OBJECT_TIME_VALUE bool "Use the BACnet basic time value object" default false