diff --git a/CMakeLists.txt b/CMakeLists.txt index a77c2e77..8cfe3657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,8 +88,8 @@ else () endif () option( - BACNET_STACK_DEPRECATED_DISABLE - "Disable deprecation compile warnings" + BACNET_STACK_DEPRECATED_DISABLE + "Disable deprecation compile warnings" ON) if(BACNET_STACK_DEPRECATED_DISABLE) @@ -239,6 +239,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/lo.h src/bacnet/basic/object/lsp.c src/bacnet/basic/object/lsp.h + src/bacnet/basic/object/lsz.c + src/bacnet/basic/object/lsz.h src/bacnet/basic/object/ms-input.c src/bacnet/basic/object/ms-input.h src/bacnet/basic/object/mso.c diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index 0e1362b5..0353d601 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -29,6 +29,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/lc.c \ $(BACNET_OBJECT_DIR)/lo.c \ $(BACNET_OBJECT_DIR)/lsp.c \ + $(BACNET_OBJECT_DIR)/lsz.c \ $(BACNET_OBJECT_DIR)/ms-input.c \ $(BACNET_OBJECT_DIR)/mso.c \ $(BACNET_OBJECT_DIR)/msv.c \ diff --git a/apps/server/Makefile b/apps/server/Makefile index 82534d1b..83026c6f 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -23,6 +23,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/lc.c \ $(BACNET_OBJECT_DIR)/lo.c \ $(BACNET_OBJECT_DIR)/lsp.c \ + $(BACNET_OBJECT_DIR)/lsz.c \ $(BACNET_OBJECT_DIR)/ms-input.c \ $(BACNET_OBJECT_DIR)/mso.c \ $(BACNET_OBJECT_DIR)/msv.c \ diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 8c00efde..34c369f8 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -63,6 +63,7 @@ #include "bacnet/basic/object/iv.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" @@ -221,6 +222,14 @@ static object_functions_t My_Object_Table[] = { 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 */ }, + { 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 */ }, { 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, diff --git a/src/bacnet/basic/object/lsz.c b/src/bacnet/basic/object/lsz.c new file mode 100644 index 00000000..2ec7f6a9 --- /dev/null +++ b/src/bacnet/basic/object/lsz.c @@ -0,0 +1,942 @@ +/** + * @file + * @author Steve Karg + * @date March 2024 + * @brief Example of a Life Safety Zone object type + * + * The Life Safety Zone object type defines a standardized object + * whose properties represent the externally visible characteristics + * associated with an arbitrary group of BACnet Life Safety Point + * and Life Safety Zone objects in fire, life safety and security + * applications. The condition of a Life Safety Zone object is + * represented by a mode and a state. + * + * @copyright + * + * Copyright (C) 2024 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/config.h" /* the custom stuff */ +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +#include "bacnet/proplist.h" +/* me! */ +#include "bacnet/basic/object/lsz.h" + +struct object_data { + bool Out_Of_Service : 1; + bool Maintenance_Required : 1; + BACNET_LIFE_SAFETY_STATE Present_Value; + BACNET_LIFE_SAFETY_STATE Tracking_Value; + BACNET_LIFE_SAFETY_MODE Mode; + BACNET_SILENCED_STATE Silenced; + BACNET_LIFE_SAFETY_OPERATION Operation_Expected; + uint8_t Reliability; + const char *Object_Name; + OS_Keylist Zone_Members; +}; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* common object type */ +static const BACNET_OBJECT_TYPE Object_Type = OBJECT_LIFE_SAFETY_ZONE; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Life_Safety_Zone_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, PROP_TRACKING_VALUE, PROP_STATUS_FLAGS, + PROP_EVENT_STATE, PROP_RELIABILITY, PROP_OUT_OF_SERVICE, PROP_MODE, + PROP_ACCEPTED_MODES, PROP_SILENCED, PROP_OPERATION_EXPECTED, + PROP_ZONE_MEMBERS, PROP_MAINTENANCE_REQUIRED, -1 +}; + +static const int Life_Safety_Zone_Properties_Optional[] = { -1 }; + +static const int Life_Safety_Zone_Properties_Proprietary[] = { -1 }; + +/** + * 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 Life_Safety_Zone_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Life_Safety_Zone_Properties_Required; + } + if (pOptional) { + *pOptional = Life_Safety_Zone_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Life_Safety_Zone_Properties_Proprietary; + } + + return; +} + +/** + * @brief Determine if the object property is a member of this object instance + * @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 + */ +static bool Property_List_Member(uint32_t object_instance, int object_property) +{ + bool found = false; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + + (void)object_instance; + Life_Safety_Zone_Property_Lists(&pRequired, &pOptional, &pProprietary); + found = property_list_member(pRequired, object_property); + if (!found) { + found = property_list_member(pOptional, object_property); + } + if (!found) { + found = property_list_member(pProprietary, object_property); + } + + return found; +} + +/** + * @brief Determines if a given object instance is valid + * @param object_instance - object-instance number of the object + * @return true if the instance is valid, and false if not + */ +bool Life_Safety_Zone_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + return true; + } + + return false; +} + +/** + * @brief Determines the number of objects + * @return Number of objects + */ +unsigned Life_Safety_Zone_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * @brief Determines if a given object instance is valid + * @param object_instance - object-instance number of the object + * @return true if the instance is BACNET_SILENCED_STATE Silenced; + + * @brief Determines the object instance-number for a given 0..N index + * where N is Life_Safety_Zone_Count(). + * @param index - 0..N value + * @return object instance-number for the given index + */ +uint32_t Life_Safety_Zone_Index_To_Instance(unsigned index) +{ + KEY key = UINT32_MAX; + + Keylist_Index_Key(Object_List, index, &key); + + return key; +} + +/** + * @brief For a given object instance-number, determines a 0..N index + * of where N is Life_Safety_Zone_Count(). + * @param object_instance - object-instance number of the object + * @return index for the given instance-number + */ +unsigned Life_Safety_Zone_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * @brief For a given object instance-number, determines the present-value + * @param object_instance - object-instance number of the object + * @return present-value of the object + */ +BACNET_LIFE_SAFETY_STATE Life_Safety_Zone_Present_Value( + uint32_t object_instance) +{ + BACNET_LIFE_SAFETY_STATE value = LIFE_SAFETY_STATE_QUIET; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Present_Value; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the present-value + * @param object_instance - object-instance number of the object + * @param value - floating point analog output relinquish-default value + * @return true if values are within range and relinquish-default value is set. + */ +bool Life_Safety_Zone_Present_Value_Set( + uint32_t object_instance, BACNET_LIFE_SAFETY_STATE value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Present_Value = value; + status = true; + } + + return status; +} + +/** + * 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 Life_Safety_Zone_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[32]; + + 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), "LIFE-SAFETY-POINT-%u", + object_instance); + status = characterstring_init_ansi(object_name, name_text); + } + } + + return status; +} + +/** + * @brief For a given object instance-number, gets the property value + * @param object_instance - object-instance number of the object + * @return property value + */ +BACNET_SILENCED_STATE Life_Safety_Zone_Silenced(uint32_t object_instance) +{ + BACNET_SILENCED_STATE value = SILENCED_STATE_UNSILENCED; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Silenced; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the property value + * @param object_instance - object-instance number of the object + * @param value - enumerated value + * @return true if values are within range and property is set. + */ +bool Life_Safety_Zone_Silenced_Set( + uint32_t object_instance, BACNET_SILENCED_STATE value) +{ + struct object_data *pObject; + bool status = false; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= SILENCED_STATE_PROPRIETARY_MAX) { + pObject->Silenced = value; + status = true; + } + } + + return status; +} + +/** + * @brief For a given object instance-number, gets the property value + * @param object_instance - object-instance number of the object + * @return property value + */ +BACNET_LIFE_SAFETY_MODE Life_Safety_Zone_Mode(uint32_t object_instance) +{ + BACNET_LIFE_SAFETY_MODE value = LIFE_SAFETY_MODE_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Mode; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the property value + * @param object_instance - object-instance number of the object + * @param value - enumerated value + * @return true if values are within range and property is set. + */ +bool Life_Safety_Zone_Mode_Set( + uint32_t object_instance, BACNET_LIFE_SAFETY_MODE value) +{ + struct object_data *pObject; + bool status = false; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= LIFE_SAFETY_MODE_PROPRIETARY_MAX) { + pObject->Mode = value; + status = true; + } + } + + return status; +} + +/** + * @brief For a given object instance-number, gets the property value + * @param object_instance - object-instance number of the object + * @return property value + */ +BACNET_LIFE_SAFETY_OPERATION Life_Safety_Zone_Operation_Expected( + uint32_t object_instance) +{ + BACNET_LIFE_SAFETY_OPERATION value = LIFE_SAFETY_OP_NONE; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Operation_Expected; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the property value + * @param object_instance - object-instance number of the object + * @param value - enumerated value + * @return true if values are within range and property is set. + */ +bool Life_Safety_Zone_Operation_Expected_Set( + uint32_t object_instance, BACNET_LIFE_SAFETY_OPERATION value) +{ + struct object_data *pObject; + bool status = false; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= LIFE_SAFETY_OP_PROPRIETARY_MAX) { + pObject->Operation_Expected = value; + status = true; + } + } + + return status; +} + +/** + * @brief For a given object instance-number, returns the out-of-service + * status flag + * @param object_instance - object-instance number of the object + * @return out-of-service status flag + */ +bool Life_Safety_Zone_Out_Of_Service(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Out_Of_Service; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the out-of-service status + * flag + * @param object_instance - object-instance number of the object + * @param value - boolean out-of-service value + * @return true if the out-of-service status flag was set + */ +void Life_Safety_Zone_Out_Of_Service_Set(uint32_t object_instance, bool value) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Out_Of_Service != value) { + pObject->Out_Of_Service = value; + } + } +} + +/** + * @brief For a given object instance-number, gets the reliability. + * @param object_instance - object-instance number of the object + * @return reliability value + */ +BACNET_RELIABILITY Life_Safety_Zone_Reliability(uint32_t object_instance) +{ + BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + reliability = (BACNET_RELIABILITY)pObject->Reliability; + } + + return reliability; +} + +/** + * @brief For a given object instance-number, sets the reliability + * @param object_instance - object-instance number of the object + * @param value - reliability enumerated value + * @return true if values are within range and property is set. + */ +bool Life_Safety_Zone_Reliability_Set( + uint32_t object_instance, BACNET_RELIABILITY value) +{ + struct object_data *pObject; + bool status = false; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (value <= 255) { + pObject->Reliability = value; + status = true; + } + } + + return status; +} + +/** + * @brief Encode a Zone Members list complex data type + * + * @param object_instance - object-instance number of the object + * @param apdu - the APDU buffer + * @param apdu_size - size of the apdu buffer. + * + * @return bytes encoded or zero on error. + */ +static int Life_Safety_Zone_Members_Encode( + uint32_t object_instance, uint8_t *apdu, int apdu_size) +{ + struct object_data *pObject; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *data; + int apdu_len = 0, len; + unsigned index = 0; + unsigned size = 0; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return BACNET_STATUS_ERROR; + } + size = Keylist_Count(pObject->Zone_Members); + for (index = 0; index < size; index++) { + data = Keylist_Data_Index(pObject->Zone_Members, index); + len = bacapp_encode_device_obj_property_ref(NULL, data); + apdu_len += len; + } + if (apdu_len > apdu_size) { + return BACNET_STATUS_ABORT; + } + apdu_len = 0; + for (index = 0; index < size; index++) { + data = Keylist_Data_Index(pObject->Zone_Members, index); + len = bacapp_encode_device_obj_property_ref(apdu, data); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + + return apdu_len; +} + +/** + * @brief Add a member to the Zone Members list + * @param object_instance - object-instance number of the object + * @param data - the device object property reference to add + * @return true if the member was added + */ +bool Life_Safety_Zone_Members_Add( + uint32_t object_instance, + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *data) +{ + bool status = false; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *entry; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return false; + } + entry = calloc(1, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); + if (!entry) { + return false; + } + memcpy(entry, data, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); + status = Keylist_Data_Add( + pObject->Zone_Members, Keylist_Count(pObject->Zone_Members), entry); + + + return status; +} + +/** + * @brief Remove all members from the Zone Members list + * @param object_instance - object-instance number of the object +*/ +void Life_Safety_Zone_Members_Clear( + uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + Keylist_Data_Free(pObject->Zone_Members); + } +} + +/** + * @brief Write a list of Zone Members to the object + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * @return true if the list was written +*/ +static bool Life_Safety_Zone_Members_Write(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + int len = 0, apdu_len = 0, apdu_size = 0; + uint8_t *apdu = NULL; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE data = { 0 }; + + if ((wp_data == NULL) || (wp_data->application_data == NULL)) { + return false; + } + /* empty the list */ + Life_Safety_Zone_Members_Clear(wp_data->object_instance); + apdu = wp_data->application_data; + apdu_size = wp_data->application_data_len; + /* decode all packed */ + while (apdu_len < apdu_size) { + len = bacnet_device_object_property_reference_decode( + apdu, apdu_size - apdu_len, &data); + if (len < 0) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + return false; + } + Life_Safety_Zone_Members_Add(wp_data->object_instance, &data); + apdu_len += len; + } + + return true; +} + +/** + * @brief For a given object instance-number, returns the maintenance-required + * status flag + * @param object_instance - object-instance number of the object + * @return property value status flag + */ +bool Life_Safety_Zone_Maintenance_Required(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Maintenance_Required; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the maintenance-required status + * flag + * @param object_instance - object-instance number of the object + * @param value - boolean property value + * @return true if the property flag was set + */ +void Life_Safety_Zone_Maintenance_Required_Set(uint32_t object_instance, bool value) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Maintenance_Required != value) { + pObject->Maintenance_Required = value; + } + } +} + +/** + * @brief 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 Life_Safety_Zone_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_LIFE_SAFETY_STATE present_value = LIFE_SAFETY_STATE_QUIET; + BACNET_LIFE_SAFETY_MODE mode = LIFE_SAFETY_MODE_DEFAULT; + BACNET_SILENCED_STATE silenced_state = SILENCED_STATE_UNSILENCED; + BACNET_LIFE_SAFETY_OPERATION operation = LIFE_SAFETY_OP_NONE; + bool state = false; + BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED; + uint8_t *apdu = NULL; + int apdu_size = 0; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + apdu_size = rpdata->application_data_len; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], Object_Type, rpdata->object_instance); + break; + case PROP_OBJECT_NAME: + Life_Safety_Zone_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], Object_Type); + break; + case PROP_PRESENT_VALUE: + present_value = + Life_Safety_Zone_Present_Value(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], present_value); + break; + case PROP_TRACKING_VALUE: + /* FIXME: tracking value is a local matter how it is derived */ + present_value = + Life_Safety_Zone_Present_Value(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], present_value); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + if (Life_Safety_Zone_Reliability(rpdata->object_instance) == + RELIABILITY_NO_FAULT_DETECTED) { + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + } else { + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, true); + } + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + if (Life_Safety_Zone_Out_Of_Service(rpdata->object_instance)) { + bitstring_set_bit( + &bit_string, STATUS_FLAG_OUT_OF_SERVICE, true); + } else { + bitstring_set_bit( + &bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + } + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = + encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + state = Life_Safety_Zone_Out_Of_Service(rpdata->object_instance); + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_RELIABILITY: + reliability = Life_Safety_Zone_Reliability(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], reliability); + break; + case PROP_MODE: + mode = Life_Safety_Zone_Mode(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], mode); + break; + case PROP_ACCEPTED_MODES: + for (mode = MIN_LIFE_SAFETY_MODE; mode < MAX_LIFE_SAFETY_MODE; + mode++) { + len = encode_application_enumerated(&apdu[apdu_len], mode); + apdu_len += len; + } + break; + case PROP_SILENCED: + silenced_state = Life_Safety_Zone_Silenced(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], silenced_state); + break; + case PROP_OPERATION_EXPECTED: + operation = + Life_Safety_Zone_Operation_Expected(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], operation); + break; + case PROP_ZONE_MEMBERS: + apdu_len = Life_Safety_Zone_Members_Encode( + rpdata->object_instance, &apdu[0], apdu_size); + break; + case PROP_MAINTENANCE_REQUIRED: + state = false; + apdu_len = encode_application_boolean(&apdu[0], state); + 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) && (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; +} + +/* returns true if successful */ +bool Life_Safety_Zone_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* decode the some of the request */ + len = bacapp_decode_application_data( + wp_data->application_data, wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + 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; + } + /* only array properties can have array options */ + if (wp_data->array_index != BACNET_ARRAY_ALL) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + switch (wp_data->object_property) { + case PROP_MODE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated <= MAX_LIFE_SAFETY_MODE) { + Life_Safety_Zone_Mode_Set(wp_data->object_instance, + (BACNET_LIFE_SAFETY_MODE)value.type.Enumerated); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_PRESENT_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated <= UINT16_MAX) { + Life_Safety_Zone_Present_Value_Set(wp_data->object_instance, + (BACNET_LIFE_SAFETY_STATE)value.type.Enumerated); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_OUT_OF_SERVICE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Life_Safety_Zone_Out_Of_Service_Set( + wp_data->object_instance, value.type.Boolean); + } + break; + case PROP_SILENCED: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated <= UINT16_MAX) { + Life_Safety_Zone_Silenced_Set(wp_data->object_instance, + (BACNET_SILENCED_STATE)value.type.Enumerated); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_OPERATION_EXPECTED: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated <= UINT16_MAX) { + Life_Safety_Zone_Operation_Expected_Set( + wp_data->object_instance, + (BACNET_LIFE_SAFETY_OPERATION)value.type.Enumerated); + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_ZONE_MEMBERS: + status = Life_Safety_Zone_Members_Write(wp_data); + break; + default: + if (Property_List_Member( + wp_data->object_instance, 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 Creates an object and initialize its properties to defaults + * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE + */ +uint32_t Life_Safety_Zone_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) { + pObject->Object_Name = NULL; + pObject->Reliability = RELIABILITY_NO_FAULT_DETECTED; + pObject->Mode = LIFE_SAFETY_MODE_DEFAULT; + pObject->Present_Value = LIFE_SAFETY_STATE_QUIET; + pObject->Silenced = SILENCED_STATE_UNSILENCED; + pObject->Operation_Expected = LIFE_SAFETY_OP_NONE; + pObject->Maintenance_Required = false; + pObject->Out_Of_Service = false; + pObject->Zone_Members = Keylist_Create(); + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index < 0) { + free(pObject); + return BACNET_MAX_INSTANCE; + } + } else { + return BACNET_MAX_INSTANCE; + } + } + + return object_instance; +} + +/** + * @brief Deletes an object and its property + * @param object_instance - object-instance number of the object + * @return true if the object-instance was deleted + */ +bool Life_Safety_Zone_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + Keylist_Data_Free(pObject->Zone_Members); + Keylist_Delete(pObject->Zone_Members); + free(pObject); + status = true; + } + + return status; +} + +/** + * @brief Deletes all the objects and their property data + */ +void Life_Safety_Zone_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; + } +} + +/** + * @brief Initializes the object data + */ +void Life_Safety_Zone_Init(void) +{ + Object_List = Keylist_Create(); +} diff --git a/src/bacnet/basic/object/lsz.h b/src/bacnet/basic/object/lsz.h new file mode 100644 index 00000000..a300bad7 --- /dev/null +++ b/src/bacnet/basic/object/lsz.h @@ -0,0 +1,149 @@ +/** + * @file + * @author Steve Karg + * @date March 2024 + * @brief Example API for Life Safety Zone Object + * + * The Life Safety Zone object type defines a standardized object + * whose properties represent the externally visible characteristics + * associated with an arbitrary group of BACnet Life Safety Point + * and Life Safety Zone objects in fire, life safety and security + * applications. The condition of a Life Safety Zone object is + * represented by a mode and a state. + * + * @copyright + * + * Copyright (C) 2024 Steve Karg + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_LIFE_SAFETY_ZONE_H +#define BACNET_LIFE_SAFETY_ZONE_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + BACNET_STACK_EXPORT + void Life_Safety_Zone_Property_Lists( + const int **pRequired, + const int **pOptional, + const int **pProprietary); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Valid_Instance( + uint32_t object_instance); + BACNET_STACK_EXPORT + unsigned Life_Safety_Zone_Count( + void); + BACNET_STACK_EXPORT + uint32_t Life_Safety_Zone_Index_To_Instance( + unsigned index); + BACNET_STACK_EXPORT + unsigned Life_Safety_Zone_Instance_To_Index( + uint32_t object_instance); + + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Object_Name( + uint32_t object_instance, + BACNET_CHARACTER_STRING * object_name); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Name_Set( + uint32_t object_instance, + char *new_name); + + + BACNET_STACK_EXPORT + BACNET_LIFE_SAFETY_STATE Life_Safety_Zone_Present_Value( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Present_Value_Set( + uint32_t object_instance, + BACNET_LIFE_SAFETY_STATE present_value); + + BACNET_STACK_EXPORT + BACNET_SILENCED_STATE Life_Safety_Zone_Silenced( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Silenced_Set( + uint32_t object_instance, + BACNET_SILENCED_STATE value); + BACNET_STACK_EXPORT + BACNET_LIFE_SAFETY_MODE Life_Safety_Zone_Mode( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Mode_Set( + uint32_t object_instance, + BACNET_LIFE_SAFETY_MODE value); + BACNET_STACK_EXPORT + BACNET_LIFE_SAFETY_OPERATION Life_Safety_Zone_Operation_Expected( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Operation_Expected_Set( + uint32_t object_instance, + BACNET_LIFE_SAFETY_OPERATION value); + + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Out_Of_Service( + uint32_t instance); + BACNET_STACK_EXPORT + void Life_Safety_Zone_Out_Of_Service_Set( + uint32_t instance, + bool oos_flag); + + BACNET_STACK_EXPORT + BACNET_RELIABILITY Life_Safety_Zone_Reliability( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Reliability_Set( + uint32_t object_instance, BACNET_RELIABILITY value); + + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Members_Add( + uint32_t object_instance, + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *data); + BACNET_STACK_EXPORT + void Life_Safety_Zone_Members_Clear( + uint32_t object_instance); + + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Maintenance_Required( + uint32_t object_instance); + BACNET_STACK_EXPORT + void Life_Safety_Zone_Maintenance_Required_Set( + uint32_t object_instance, + bool value); + + BACNET_STACK_EXPORT + int Life_Safety_Zone_Read_Property( + BACNET_READ_PROPERTY_DATA * rpdata); + + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Write_Property( + BACNET_WRITE_PROPERTY_DATA * wp_data); + + BACNET_STACK_EXPORT + uint32_t Life_Safety_Zone_Create( + uint32_t object_instance); + BACNET_STACK_EXPORT + bool Life_Safety_Zone_Delete( + uint32_t object_instance); + BACNET_STACK_EXPORT + void Life_Safety_Zone_Cleanup( + void); + BACNET_STACK_EXPORT + void Life_Safety_Zone_Init( + void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/sys/keylist.c b/src/bacnet/basic/sys/keylist.c index 9e4c6fd5..1e097896 100644 --- a/src/bacnet/basic/sys/keylist.c +++ b/src/bacnet/basic/sys/keylist.c @@ -301,6 +301,20 @@ void *Keylist_Data_Delete(OS_Keylist list, KEY key) return data; } +/** + * @brief Pops every node data, removing it from the list, + * and frees the data memory + * @param list Pointer to the list + * */ +void Keylist_Data_Free(OS_Keylist list) +{ + void *data; + while (Keylist_Count(list) > 0) { + data = Keylist_Data_Pop(list); + free(data); + } +} + /** Returns the data from last node, and * removes it from the list. * @@ -411,7 +425,7 @@ KEY Keylist_Key(OS_Keylist list, int index) return key; } -/** +/** * Determine if there is a node key at the given index. * * @param list Pointer to the list diff --git a/src/bacnet/basic/sys/keylist.h b/src/bacnet/basic/sys/keylist.h index 5be3eff9..775ca1fc 100644 --- a/src/bacnet/basic/sys/keylist.h +++ b/src/bacnet/basic/sys/keylist.h @@ -87,6 +87,11 @@ extern "C" { void *Keylist_Data_Pop( OS_Keylist list); +/* removes and frees all data from the list */ + BACNET_STACK_EXPORT + void Keylist_Data_Free( + OS_Keylist list); + /* returns the data from the node specified by key */ BACNET_STACK_EXPORT void *Keylist_Data( @@ -115,8 +120,8 @@ extern "C" { /* returns the node key specified by the index */ BACNET_STACK_EXPORT bool Keylist_Index_Key( - OS_Keylist list, - int index, + OS_Keylist list, + int index, KEY *pKey); /* returns the next empty key from the list */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4d79cb49..cef431b8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -130,6 +130,7 @@ list(APPEND testdirs #bacnet/basic/object/lc #Tests skipped, redesign to use only API bacnet/basic/object/lo bacnet/basic/object/lsp + bacnet/basic/object/lsz bacnet/basic/object/ms-input bacnet/basic/object/mso bacnet/basic/object/msv diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index 13a6e6b5..e42d2e09 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/lc.c ${SRC_DIR}/bacnet/basic/object/lo.c ${SRC_DIR}/bacnet/basic/object/lsp.c + ${SRC_DIR}/bacnet/basic/object/lsz.c ${SRC_DIR}/bacnet/basic/object/ms-input.c ${SRC_DIR}/bacnet/basic/object/mso.c ${SRC_DIR}/bacnet/basic/object/msv.c diff --git a/test/bacnet/basic/object/lsz/CMakeLists.txt b/test/bacnet/basic/object/lsz/CMakeLists.txt new file mode 100644 index 00000000..d8252089 --- /dev/null +++ b/test/bacnet/basic/object/lsz/CMakeLists.txt @@ -0,0 +1,67 @@ +# 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/lsz.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/bacerror.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/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/datetime.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 + # 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/lsz/src/main.c b/test/bacnet/basic/object/lsz/src/main.c new file mode 100644 index 00000000..ac71209a --- /dev/null +++ b/test/bacnet/basic/object/lsz/src/main.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 Legrand North America, LLC. + * + * SPDX-License-Identifier: MIT + */ + +/* @file + * @brief test BACnet integer encode/decode APIs + */ + +#include +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(testsLifeSafetyZone, testLifeSafetyZone) +#else +static void testLifeSafetyZone(void) +#endif +{ + bool status = false; + unsigned count = 0; + uint32_t object_instance = 0, test_object_instance = 0; + const int skip_fail_property_list[] = { -1 }; + + Life_Safety_Zone_Init(); + object_instance = Life_Safety_Zone_Create(BACNET_MAX_INSTANCE); + count = Life_Safety_Zone_Count(); + zassert_true(count > 0, NULL); + test_object_instance = Life_Safety_Zone_Index_To_Instance(0); + zassert_equal(test_object_instance, object_instance, NULL); + bacnet_object_properties_read_write_test( + OBJECT_LIFE_SAFETY_ZONE, + object_instance, + Life_Safety_Zone_Property_Lists, + Life_Safety_Zone_Read_Property, + Life_Safety_Zone_Write_Property, + skip_fail_property_list); +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(testsLifeSafetyZone, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(testsLifeSafetyZone, + ztest_unit_test(testLifeSafetyZone) + ); + + ztest_run_test_suite(testsLifeSafetyZone); +} +#endif