From 8f576461a8a6f2b5781c676457200da142e0c5a7 Mon Sep 17 00:00:00 2001 From: Mikhail Antropov <103285038+Michail-Antropov@users.noreply.github.com> Date: Fri, 2 Feb 2024 08:01:27 +0300 Subject: [PATCH] Feature/oscbs 29 calendar time object (#440) * Added basic Calendar object, unit tests, and integration with example device object. * Added basic Time Value object, unit tests, and integration with example device object. --------- Co-authored-by: Steve Karg --- .github/workflows/gcc.yml | 32 +- CMakeLists.txt | 4 + apps/gateway/Makefile | 2 + apps/server/Makefile | 2 + ports/arduino_uno/Makefile | 4 +- ports/pic18f97j60/Makefile | 19 - .../BACnet_Object_Definitions.vcxproj | 2 + .../BACnet_Object_Definitions.vcxproj.filters | 6 + src/bacnet/bacapp.c | 3 +- src/bacnet/basic/object/calendar.c | 801 ++++++++++++++++++ src/bacnet/basic/object/calendar.h | 105 +++ src/bacnet/basic/object/device.c | 18 + src/bacnet/basic/object/time_value.c | 741 ++++++++++++++++ src/bacnet/basic/object/time_value.h | 101 +++ src/bacnet/calendar_entry.c | 4 + test/CMakeLists.txt | 2 + test/bacnet/basic/object/blo/src/main.c | 6 +- .../basic/object/calendar/CMakeLists.txt | 69 ++ test/bacnet/basic/object/calendar/src/main.c | 239 ++++++ test/bacnet/basic/object/calendar/stubs.c | 28 + .../bacnet/basic/object/device/CMakeLists.txt | 2 + .../basic/object/time_value/CMakeLists.txt | 69 ++ .../bacnet/basic/object/time_value/src/main.c | 141 +++ test/bacnet/basic/object/time_value/stubs.c | 28 + test/bacnet/create_object/CMakeLists.txt | 1 - test/bacnet/datetime/src/main.c | 11 +- zephyr/CMakeLists.txt | 4 + zephyr/subsys/object/CMakeLists.txt | 2 + .../basic/object/calendar/CMakeLists.txt | 74 ++ .../bacnet/basic/object/calendar/prj.conf | 2 + .../object/calendar/prj.unit_testing.conf | 2 + .../basic/object/calendar/testcase.yaml | 6 + .../bacnet/basic/object/device/CMakeLists.txt | 3 + .../basic/object/time_value/CMakeLists.txt | 74 ++ .../bacnet/basic/object/time_value/prj.conf | 2 + .../object/time_value/prj.unit_testing.conf | 2 + .../basic/object/time_value/testcase.yaml | 6 + zephyr/tests/bacnet/event/CMakeLists.txt | 2 +- 38 files changed, 2574 insertions(+), 45 deletions(-) create mode 100644 src/bacnet/basic/object/calendar.c create mode 100644 src/bacnet/basic/object/calendar.h create mode 100644 src/bacnet/basic/object/time_value.c create mode 100644 src/bacnet/basic/object/time_value.h create mode 100644 test/bacnet/basic/object/calendar/CMakeLists.txt create mode 100644 test/bacnet/basic/object/calendar/src/main.c create mode 100644 test/bacnet/basic/object/calendar/stubs.c create mode 100644 test/bacnet/basic/object/time_value/CMakeLists.txt create mode 100644 test/bacnet/basic/object/time_value/src/main.c create mode 100644 test/bacnet/basic/object/time_value/stubs.c create mode 100644 zephyr/tests/bacnet/basic/object/calendar/CMakeLists.txt create mode 100644 zephyr/tests/bacnet/basic/object/calendar/prj.conf create mode 100644 zephyr/tests/bacnet/basic/object/calendar/prj.unit_testing.conf create mode 100644 zephyr/tests/bacnet/basic/object/calendar/testcase.yaml create mode 100644 zephyr/tests/bacnet/basic/object/time_value/CMakeLists.txt create mode 100644 zephyr/tests/bacnet/basic/object/time_value/prj.conf create mode 100644 zephyr/tests/bacnet/basic/object/time_value/prj.unit_testing.conf create mode 100644 zephyr/tests/bacnet/basic/object/time_value/testcase.yaml diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml index 89bacf86..d4e5e76c 100644 --- a/.github/workflows/gcc.yml +++ b/.github/workflows/gcc.yml @@ -21,19 +21,19 @@ jobs: run: | gcc --version make clean - make CSTANDARD="-std=gnu89" all + make LEGACY=true CSTANDARD="-std=gnu89" all - name: Build Demo Apps GNU99 run: | make clean make CSTANDARD="-std=gnu99" all - - name: Build Demo Apps GNU11 + - name: LEGACY=true Build Demo Apps GNU11 run: | make clean - make CSTANDARD="-std=gnu11" all + make LEGACY=true CSTANDARD="-std=gnu11" all - name: Build Demo Apps GNU17 run: | make clean - make CSTANDARD="-std=gnu17" all + make LEGACY=true CSTANDARD="-std=gnu17" all bip-no-bbmd-apps: runs-on: ubuntu-latest @@ -47,7 +47,7 @@ jobs: run: | gcc --version make clean - make BBMD=none all + make LEGACY=true BBMD=none all bip-client-bbmd-apps: runs-on: ubuntu-latest @@ -61,7 +61,7 @@ jobs: run: | gcc --version make clean - make BBMD=client all + make LEGACY=true BBMD=client all gateway: runs-on: ubuntu-latest @@ -75,7 +75,7 @@ jobs: run: | gcc --version make clean - make gateway + make LEGACY=true gateway router: runs-on: ubuntu-latest @@ -89,7 +89,7 @@ jobs: run: | gcc --version make clean - make router + make LEGACY=true router router-ipv6: runs-on: ubuntu-latest @@ -103,7 +103,7 @@ jobs: run: | gcc --version make clean - make router-ipv6 + make LEGACY=true router-ipv6 router-mstp: runs-on: ubuntu-latest @@ -117,7 +117,7 @@ jobs: run: | gcc --version make clean - make router-mstp + make LEGACY=true router-mstp bip6: runs-on: ubuntu-latest @@ -131,7 +131,7 @@ jobs: run: | gcc --version make clean - make bip6 + make LEGACY=true bip6 mstp: runs-on: ubuntu-latest @@ -145,7 +145,7 @@ jobs: run: | gcc --version make clean - make mstp + make LEGACY=true mstp ethernet: runs-on: ubuntu-latest @@ -159,7 +159,7 @@ jobs: run: | gcc --version make clean - make ethernet + make LEGACY=true ethernet ports-arm-makefile: runs-on: ubuntu-latest @@ -175,9 +175,9 @@ jobs: run: | make clean arm-none-eabi-gcc --version - make stm32f10x - make stm32f4xx - make at91sam7s + make LEGACY=true stm32f10x + make LEGACY=true stm32f4xx + make LEGACY=true at91sam7s ports-arm-cmake: runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index fb11b46b..fc363a5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -198,6 +198,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/bv.h src/bacnet/basic/object/channel.c src/bacnet/basic/object/channel.h + src/bacnet/basic/object/calendar.c + src/bacnet/basic/object/calendar.h src/bacnet/basic/object/color_object.c src/bacnet/basic/object/color_object.h src/bacnet/basic/object/color_temperature.c @@ -238,6 +240,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/time_value.c + src/bacnet/basic/object/time_value.h src/bacnet/basic/object/trendlog.c src/bacnet/basic/object/trendlog.h src/bacnet/basic/service/h_alarm_ack.c diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index 56ce1583..0e1362b5 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -18,6 +18,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/blo.c \ $(BACNET_OBJECT_DIR)/bo.c \ $(BACNET_OBJECT_DIR)/bv.c \ + $(BACNET_OBJECT_DIR)/calendar.c \ $(BACNET_OBJECT_DIR)/channel.c \ $(BACNET_OBJECT_DIR)/color_object.c \ $(BACNET_OBJECT_DIR)/color_temperature.c \ @@ -35,6 +36,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/piv.c \ $(BACNET_OBJECT_DIR)/nc.c \ $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_OBJECT_DIR)/time_value.c \ $(BACNET_OBJECT_DIR)/trendlog.c \ $(BACNET_OBJECT_DIR)/schedule.c \ $(BACNET_OBJECT_DIR)/access_credential.c \ diff --git a/apps/server/Makefile b/apps/server/Makefile index e3039448..82534d1b 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -13,6 +13,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/bo.c \ $(BACNET_OBJECT_DIR)/blo.c \ $(BACNET_OBJECT_DIR)/bv.c \ + $(BACNET_OBJECT_DIR)/calendar.c \ $(BACNET_OBJECT_DIR)/channel.c \ $(BACNET_OBJECT_DIR)/color_object.c \ $(BACNET_OBJECT_DIR)/color_temperature.c \ @@ -29,6 +30,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/piv.c \ $(BACNET_OBJECT_DIR)/nc.c \ $(BACNET_OBJECT_DIR)/netport.c \ + $(BACNET_OBJECT_DIR)/time_value.c \ $(BACNET_OBJECT_DIR)/trendlog.c \ $(BACNET_OBJECT_DIR)/schedule.c \ $(BACNET_OBJECT_DIR)/access_credential.c \ diff --git a/ports/arduino_uno/Makefile b/ports/arduino_uno/Makefile index 135c4ba1..b996a6d7 100644 --- a/ports/arduino_uno/Makefile +++ b/ports/arduino_uno/Makefile @@ -73,9 +73,7 @@ CORESRC = \ $(BACNET_CORE)/abort.c \ $(BACNET_CORE)/reject.c \ $(BACNET_CORE)/bacerror.c \ - $(BACNET_CORE)/bacapp.c \ -# $(BACNET_CORE)/ethernet.c - + $(BACNET_CORE)/bacapp.c ## Include Directories INCLUDES = -I. -I$(BACNET_INCLUDE) diff --git a/ports/pic18f97j60/Makefile b/ports/pic18f97j60/Makefile index f0851c3a..672e7b36 100644 --- a/ports/pic18f97j60/Makefile +++ b/ports/pic18f97j60/Makefile @@ -52,25 +52,6 @@ CORESRC = \ $(BACNET_CORE)/bacapp.c \ $(BACNET_CORE)/version.c -# $(BACNET_CORE)/bacprop.c \ -# $(BACNET_CORE)/bactext.c \ -# $(BACNET_CORE)/datetime.c \ -# $(BACNET_CORE)/indtext.c \ -# $(BACNET_CORE)/bigend.c \ -# $(BACNET_CORE)/arf.c \ -# $(BACNET_CORE)/awf.c \ -# $(BACNET_CORE)/cov.c \ -# $(BACNET_CORE)/dcc.c \ -# $(BACNET_CORE)/iam/iam_client.c \ -# $(BACNET_CORE)/ihave.c \ -# $(BACNET_CORE)/rd.c \ -# $(BACNET_CORE)/rpm.c \ -# $(BACNET_CORE)/timesync.c \ -# $(BACNET_CORE)/whohas.c \ -# $(BACNET_CORE)/filename.c \ -# $(BACNET_CORE)/tsm.c \ -# $(BACNET_CORE)/address.c \ - ## Include Directories INCLUDES = -I. -I$(BACNET_INCLUDE) 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 d695bfac..cbfd6024 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 @@ -182,6 +182,7 @@ + @@ -197,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 5a0283fd..ddce119b 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 @@ -36,6 +36,9 @@ Source Files + + Source Files + Source Files @@ -69,6 +72,9 @@ Source Files + + Source Files + Source Files diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index ca03126a..7457b17f 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -1320,7 +1320,8 @@ int bacapp_known_property_tag( * @param max_apdu_len - number of bytes in the buffer * @param value - stores the decoded property value * @param property - context property identifier - * @return number of bytes decoded, or ERROR if errors occur + * @return number of bytes decoded, or BACNET_STATUS_ERROR if errors occur + * @note number of bytes can be 0 for empty lists, etc. */ int bacapp_decode_known_property(uint8_t *apdu, int max_apdu_len, diff --git a/src/bacnet/basic/object/calendar.c b/src/bacnet/basic/object/calendar.c new file mode 100644 index 00000000..69f66eb1 --- /dev/null +++ b/src/bacnet/basic/object/calendar.c @@ -0,0 +1,801 @@ +/** + * @file + * @author Steve Karg + * @author Mikhail Antropov + * @date June 2022 + * @brief Calendar objects, customize for your use + * + * @section DESCRIPTION + * + * The Calendar object is an object with a present-value that + * uses a BOOLEAN data type, and features a Date_List + * that is a BACnetLIST of BACnetCalendarEntry. + * + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/abort.h" +#include "bacnet/apdu.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacapp.h" +#include "bacnet/bactext.h" +#include "bacnet/cov.h" +#include "bacnet/npdu.h" +#include "bacnet/proplist.h" +#include "bacnet/reject.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "calendar.h" + +struct object_data { + bool Changed : 1; + bool Write_Enabled : 1; + bool Present_Value; + OS_Keylist Date_List; + const char *Object_Name; + const char *Description; +}; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static calendar_write_present_value_callback Calendar_Write_Present_Value_Callback; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Calendar_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_DATE_LIST, + -1 }; + +static const int Calendar_Properties_Optional[] = { PROP_DESCRIPTION, -1 }; + +static const int Calendar_Properties_Proprietary[] = { -1 }; + +/* standard properties that are arrays for this object, + but not necessary supported in this object */ +static const int BACnetARRAY_Properties[] = { + PROP_PRIORITY_ARRAY, PROP_TAGS, -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 Calendar_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Calendar_Properties_Required; + } + if (pOptional) { + *pOptional = Calendar_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Calendar_Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Calendar instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Calendar_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 Calendar objects + * + * @return Number of Calendar objects + */ +unsigned Calendar_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Calendar objects where N is Calendar_Count(). + * + * @param index - 0..N where N is Calendar_Count() + * + * @return object instance-number for the given index + */ +uint32_t Calendar_Index_To_Instance(unsigned index) +{ + return Keylist_Key(Object_List, index); +} + +/** + * For a given object instance-number, determines a 0..N index + * of Calendar objects where N is Calendar_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or Calendar_Count() + * if not valid. + */ +unsigned Calendar_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param old_value - boolean, previous value of the Present Value property + * @param value - boolean, new value of the Present Value property + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Calendar_Present_Value_Write(uint32_t object_instance, + bool old_value, + bool value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + old_value = pObject->Present_Value; + pObject->Present_Value = value; + if (Calendar_Write_Present_Value_Callback) { + Calendar_Write_Present_Value_Callback( + object_instance, old_value, value); + } + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given date-list, deletes the entire data-list. + * @param list - the list to be deleted + */ +static void Calendar_Date_List_Clean(OS_Keylist list) +{ + void *data; + while (Keylist_Count(list) > 0) { + data = Keylist_Data_Pop(list); + free(data); + } +} + +/** + * For a given object instance-number, returns the Calendar entity by index. + * + * @param object_instance - object-instance number of the object + * @param index - index of entity + * + * @return Calendar entity. + */ +BACNET_CALENDAR_ENTRY *Calendar_Date_List_Get( + uint32_t object_instance, uint8_t index) +{ + BACNET_CALENDAR_ENTRY *entry = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + entry = Keylist_Data_Index(pObject->Date_List, index); + } + + return entry; +} + +/** + * For a given object instance-number, adds a Calendar entity to entities list. + * + * @param object_instance - object-instance number of the object + * @param entity - Calendar entity + * + * @return true if the entity is add successfully. + */ +bool Calendar_Date_List_Add(uint32_t object_instance, + BACNET_CALENDAR_ENTRY *value) +{ + bool st = false; + BACNET_CALENDAR_ENTRY *entry; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return false; + } + + entry = calloc(1, sizeof(BACNET_CALENDAR_ENTRY)); + if (!entry) { + return false; + } + + *entry = *value; + st = Keylist_Data_Add( + pObject->Date_List, Keylist_Count(pObject->Date_List), entry); + + return st; +} + +/** + * For a given object instance-number, clears to entities list. + * + * @param object_instance - object-instance number of the object + * + * @return true if entities list is clear successfully. + */ +bool Calendar_Date_List_Delete_All(uint32_t object_instance) +{ + struct object_data *pObject; + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return false; + } + + Calendar_Date_List_Clean(pObject->Date_List); + + return true; +} + +/** + * For a given object instance-number, returns the entities list length. + * + * @param object_instance - object-instance number of the object + * + * @return size of entities list. + */ +int Calendar_Date_List_Count(uint32_t object_instance) +{ + struct object_data *pObject; + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return 0; + } + + return Keylist_Count(pObject->Date_List); +} + +/** + * @brief Encode a Calendar entity 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. + */ +int Calendar_Date_List_Encode( + uint32_t object_instance, uint8_t *apdu, int max_apdu) +{ + BACNET_CALENDAR_ENTRY *entry = NULL; + int apdu_len = 0; + unsigned index = 0; + unsigned size = 0; + + size = Calendar_Date_List_Count(object_instance); + for (index = 0; index < size; index++) { + entry = Calendar_Date_List_Get(object_instance, index); + apdu_len += bacnet_calendar_entry_encode(NULL, entry); + } + if (apdu_len > max_apdu) { + return BACNET_STATUS_ABORT; + } + apdu_len = 0; + for (index = 0; index < size; index++) { + entry = Calendar_Date_List_Get(object_instance, index); + apdu_len += bacnet_calendar_entry_encode(&apdu[apdu_len], entry); + } + + return apdu_len; +} + +/** + * 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 + */ +bool Calendar_Present_Value(uint32_t object_instance) +{ + BACNET_DATE date; + BACNET_TIME time; + BACNET_CALENDAR_ENTRY *entry = NULL; + unsigned size = 0; + int index; + + datetime_local(&date, &time, NULL, NULL); + + size = Calendar_Date_List_Count(object_instance); + for (index = 0; index < size; index++) { + entry = Calendar_Date_List_Get(object_instance, index); + if (bacapp_date_in_calendar_entry(&date, entry)) { + return true; + } + } + + return false; +} + +/** + * 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 Calendar_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[16] = "CALENDAR-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), "CALENDAR-%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 Calendar_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 *Calendar_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 Calendar_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 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; + Calendar_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 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 Calendar_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; + int apdu_max = 0; + bool value = false; + + 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: + Calendar_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_PRESENT_VALUE: + value = Calendar_Present_Value(rpdata->object_instance); + apdu_len = encode_application_boolean(apdu, value); + break; + case PROP_DATE_LIST: + apdu_len = Calendar_Date_List_Encode( + rpdata->object_instance, apdu, apdu_max); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi( + &char_string, Calendar_Description(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + 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; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Calendar_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + int iOffset; + BACNET_CALENDAR_ENTRY entry; + bool pv_old; + bool pv; + + /* 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; + } + if ((!BACnetARRAY_Property(wp_data->object_property)) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + 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_DATE_LIST: + pv_old = Calendar_Present_Value(wp_data->object_instance); + Calendar_Date_List_Delete_All(wp_data->object_instance); + iOffset = 0; + /* decode all packed */ + while (iOffset < wp_data->application_data_len) { + len = bacnet_calendar_entry_decode( + &wp_data->application_data[iOffset], + wp_data->application_data_len - iOffset, + &entry); + if (len == BACNET_STATUS_REJECT) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + return false; + } + iOffset += len; + Calendar_Date_List_Add(wp_data->object_instance, &entry); + } + pv = Calendar_Present_Value(wp_data->object_instance); + status = Calendar_Present_Value_Write(wp_data->object_instance, + pv_old, pv, wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + 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 Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications + */ +void Calendar_Write_Present_Value_Callback_Set( + calendar_write_present_value_callback cb) +{ + Calendar_Write_Present_Value_Callback = cb; +} + +/** + * @brief Determines a object write-enabled flag state + * @param object_instance - object-instance number of the object + * @return write-enabled status flag + */ +bool Calendar_Write_Enabled(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Write_Enabled; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Calendar_Write_Enable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = true; + } +} + +/** + * @brief For a given object instance-number, clears the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Calendar_Write_Disable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = false; + } +} + +/** + * Creates a Calendar object + * @param object_instance - object-instance number of the object + * @return object_instance if the object is created, else BACNET_MAX_INSTANCE + */ +uint32_t Calendar_Create(uint32_t object_instance) +{ + bool status = false; + 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->Present_Value = false; + pObject->Date_List = Keylist_Create(); + pObject->Changed = false; + pObject->Write_Enabled = false; + /* 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 Calendar object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Calendar_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + Calendar_Date_List_Clean(pObject->Date_List); + Keylist_Delete(pObject->Date_List); + free(pObject); + status = true; + } + + return status; +} + +/** + * Deletes all the Calendars and their data + */ +void Calendar_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + Calendar_Date_List_Clean(pObject->Date_List); + Keylist_Delete(pObject->Date_List); + free(pObject); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the Calendar object data + */ +void Calendar_Init(void) +{ + Object_List = Keylist_Create(); + if (Object_List) { + atexit(Calendar_Cleanup); + } +} diff --git a/src/bacnet/basic/object/calendar.h b/src/bacnet/basic/object/calendar.h new file mode 100644 index 00000000..225d2d83 --- /dev/null +++ b/src/bacnet/basic/object/calendar.h @@ -0,0 +1,105 @@ +/** + * @file + * @author Mikhail Antropov + * @date June 2022 + * @brief API for a Calendar object used by a BACnet device object + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_CALENDAR_OBJECT_H +#define BACNET_CALENDAR_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/calendar_entry.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +/** + * @brief Callback for gateway write present value request + * @param object_instance - object-instance number of the object + * @param old_value - bool value prior to write + * @param value - bool value of the write + */ +typedef void (*calendar_write_present_value_callback)( + uint32_t object_instance, bool old_value, bool value); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Calendar_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Calendar_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Calendar_Count(void); +BACNET_STACK_EXPORT +uint32_t Calendar_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Calendar_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Calendar_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Calendar_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +int Calendar_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +bool Calendar_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +BACNET_STACK_EXPORT +bool Calendar_Present_Value(uint32_t object_instance); +BACNET_STACK_EXPORT +void Calendar_Write_Present_Value_Callback_Set( + calendar_write_present_value_callback cb); + +BACNET_STACK_EXPORT +BACNET_CALENDAR_ENTRY *Calendar_Date_List_Get( + uint32_t object_instance, uint8_t index); +BACNET_STACK_EXPORT +bool Calendar_Date_List_Add( + uint32_t object_instance, BACNET_CALENDAR_ENTRY *value); +BACNET_STACK_EXPORT +bool Calendar_Date_List_Delete_All(uint32_t object_instance); +BACNET_STACK_EXPORT +int Calendar_Date_List_Count(uint32_t object_instance); +BACNET_STACK_EXPORT +int Calendar_Date_List_Encode( + uint32_t object_instance, uint8_t *apdu, int max_apdu); + +BACNET_STACK_EXPORT +char *Calendar_Description(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Calendar_Description_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +bool Calendar_Write_Enabled(uint32_t instance); +BACNET_STACK_EXPORT +void Calendar_Write_Enable(uint32_t instance); +BACNET_STACK_EXPORT +void Calendar_Write_Disable(uint32_t instance); + +BACNET_STACK_EXPORT +uint32_t Calendar_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Calendar_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Calendar_Cleanup(void); +BACNET_STACK_EXPORT +void Calendar_Init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index fd73658f..8c00efde 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -56,6 +56,7 @@ #endif #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" @@ -71,6 +72,7 @@ #include "bacnet/basic/object/osv.h" #include "bacnet/basic/object/piv.h" #include "bacnet/basic/object/schedule.h" +#include "bacnet/basic/object/time_value.h" #include "bacnet/basic/object/trendlog.h" #if defined(INTRINSIC_REPORTING) #include "bacnet/basic/object/nc.h" @@ -164,6 +166,14 @@ static object_functions_t My_Object_Table[] = { NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, + { OBJECT_CALENDAR, Calendar_Init, Calendar_Count, + Calendar_Index_To_Instance, Calendar_Valid_Instance, + Calendar_Object_Name, Calendar_Read_Property, + Calendar_Write_Property, Calendar_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_CHARACTERSTRING_VALUE, CharacterString_Value_Init, CharacterString_Value_Count, CharacterString_Value_Index_To_Instance, CharacterString_Value_Valid_Instance, CharacterString_Value_Object_Name, @@ -337,6 +347,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_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, + Time_Value_Write_Property, Time_Value_Property_Lists, + NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, + NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, { OBJECT_ACCUMULATOR, Accumulator_Init, Accumulator_Count, Accumulator_Index_To_Instance, Accumulator_Valid_Instance, Accumulator_Object_Name, Accumulator_Read_Property, diff --git a/src/bacnet/basic/object/time_value.c b/src/bacnet/basic/object/time_value.c new file mode 100644 index 00000000..72af1b3d --- /dev/null +++ b/src/bacnet/basic/object/time_value.c @@ -0,0 +1,741 @@ +/** + * @file + * @author Steve Karg + * @author Mikhail Antropov + * @date June 2023 + * @brief Time Value objects used by a BACnet device object + * + * @section DESCRIPTION + * + * The Time Value object is an object with a present-value that + * uses an bacnet time data type. +* + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/bacapp.h" +#include "bacnet/bactext.h" +#include "bacnet/cov.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/wp.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "time_value.h" + +struct object_data { + bool Changed : 1; + bool Write_Enabled : 1; + bool Out_Of_Service : 1; + BACNET_TIME Present_Value; + const char *Object_Name; + const char *Description; +}; + +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static time_value_write_present_value_callback Time_Value_Write_Present_Value_Callback; + +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Time_Value_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_PRESENT_VALUE, PROP_STATUS_FLAGS, + -1 }; + +static const int Time_Value_Properties_Optional[] = { PROP_DESCRIPTION, + PROP_EVENT_STATE, PROP_OUT_OF_SERVICE, -1 }; + +static const int Time_Value_Properties_Proprietary[] = { -1 }; + +/* standard properties that are arrays for this object, + but not necessary supported in this object */ +static const int BACnetARRAY_Properties[] = { + PROP_EVENT_TIME_STAMPS, PROP_EVENT_MESSAGE_TEXTS, + PROP_EVENT_MESSAGE_TEXTS_CONFIG, + PROP_VALUE_SOURCE_ARRAY, PROP_COMMAND_TIME_ARRAY, PROP_TAGS, -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 Time_Value_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Time_Value_Properties_Required; + } + if (pOptional) { + *pOptional = Time_Value_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Time_Value_Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Time Value instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Time_Value_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 Time Value objects + * + * @return Number of Time Value objects + */ +unsigned Time_Value_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Time Value objects where N is Time_Value_Count(). + * + * @param index - 0..N where N is Time_Value_Count() + * + * @return object instance-number for the given index + */ +uint32_t Time_Value_Index_To_Instance(unsigned index) +{ + return Keylist_Key(Object_List, index); +} + +/** + * For a given object instance-number, determines a 0..N index + * of Time Value objects where N is Time_Value_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or Time_Value_Count() + * if not valid. + */ +unsigned Time_Value_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * 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 + */ +bool Time_Value_Present_Value(uint32_t object_instance, BACNET_TIME *value) +{ + bool status = false; + struct object_data *pObject; + BACNET_DATE date; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Out_Of_Service) { + *value = pObject->Present_Value; + status = true; + } else { + status = datetime_local(&date, value, NULL, NULL); + } + } + + return status; +} + +/** + * For a given object instance-number, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - indicator of 'present value' + * + * @return true if values are within range and present-value is set. + */ +bool Time_Value_Present_Value_Set(uint32_t object_instance, BACNET_TIME *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, sets the present-value + * + * @param object_instance - object-instance number of the object + * @param value - Bacnet time data object + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Time_Value_Present_Value_Write(uint32_t object_instance, + BACNET_TIME *value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + BACNET_TIME old_value = {0}; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + (void)priority; + if (pObject->Write_Enabled) { + old_value = pObject->Present_Value; + pObject->Present_Value = *value; + if (Time_Value_Write_Present_Value_Callback) { + Time_Value_Write_Present_Value_Callback( + object_instance, &old_value, value); + } + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, returns the Out-of-service value + * + * @param object_instance - object-instance number of the object + * + * @return Out-of-service value of the object + */ +bool Time_Value_Out_Of_Service(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = pObject->Out_Of_Service; + } + + return status; +} + +/** + * For a given object instance-number, sets the Out-of-service value + * + * @param object_instance - object-instance number of the object + * @param value - indicator of 'Out-of-service' + * + * @return true if Out-of-service value is set. + */ +bool Time_Value_Out_Of_Service_Set(uint32_t object_instance, bool value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Out_Of_Service = 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 Time_Value_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[16] = "Time-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), "Time-%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 Time_Value_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 *Time_Value_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 Time_Value_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 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; + Time_Value_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 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 Time_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + uint8_t *apdu = NULL; + BACNET_TIME value; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + + apdu = rpdata->application_data; + 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: + Time_Value_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_PRESENT_VALUE: + if (Time_Value_Present_Value(rpdata->object_instance, &value)) { + apdu_len = encode_application_time(apdu, &value); + } + break; + + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, + Time_Value_Out_Of_Service(rpdata->object_instance)); + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OUT_OF_SERVICE: + apdu_len = encode_application_boolean(&apdu[0], + Time_Value_Out_Of_Service(rpdata->object_instance)); + break; + case PROP_DESCRIPTION: + characterstring_init_ansi( + &char_string, Time_Value_Description(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + break; + case PROP_EVENT_STATE: + apdu_len = + encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); + 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; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Time_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + BACNET_APPLICATION_DATA_VALUE value; + int len = 0; + + /* 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; + } + if ((!BACnetARRAY_Property(wp_data->object_property)) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + 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_PRESENT_VALUE: + if (Time_Value_Out_Of_Service(wp_data->object_instance)) { + status = write_property_type_valid(wp_data, &value, + BACNET_APPLICATION_TAG_TIME); + if (status) { + status = Time_Value_Present_Value_Write( + wp_data->object_instance, + &value.type.Time, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + 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 Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications + */ +void Time_Value_Write_Present_Value_Callback_Set( + time_value_write_present_value_callback cb) +{ + Time_Value_Write_Present_Value_Callback = cb; +} + +/** + * @brief Determines the status flags for a given object instance-number + * @param object_instance - object-instance number of the object + * @return status flags bitstring octet + */ +uint8_t Time_Value_Status_Flags(uint32_t object_instance) +{ + BACNET_BIT_STRING bit_string; + + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, + Time_Value_Out_Of_Service(object_instance)); + + return bitstring_octet(&bit_string, 0); +} + +/** + * @brief Determines a object write-enabled flag state + * @param object_instance - object-instance number of the object + * @return write-enabled status flag + */ +bool Time_Value_Write_Enabled(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Write_Enabled; + } + + return value; +} + +/** + * @brief For a given object instance-number, sets the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Time_Value_Write_Enable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = true; + } +} + +/** + * @brief For a given object instance-number, clears the write-enabled flag + * @param object_instance - object-instance number of the object + */ +void Time_Value_Write_Disable(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Write_Enabled = false; + } +} + +/** + * Creates a Time Value object + * @param object_instance - object-instance number of the object + * @return object_instance if the object is created, else BACNET_MAX_INSTANCE + */ +uint32_t Time_Value_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; + memset(&pObject->Present_Value, 0, sizeof(pObject->Present_Value)); + pObject->Changed = false; + pObject->Write_Enabled = false; + /* 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 Time Value object + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Time_Value_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 Time_Value_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 Time Value object data + */ +void Time_Value_Init(void) +{ + Object_List = Keylist_Create(); + if (Object_List) { + atexit(Time_Value_Cleanup); + } +} diff --git a/src/bacnet/basic/object/time_value.h b/src/bacnet/basic/object/time_value.h new file mode 100644 index 00000000..ef5eea5d --- /dev/null +++ b/src/bacnet/basic/object/time_value.h @@ -0,0 +1,101 @@ +/** + * @file + * @author Mikhail Antropov + * @date June 2023 + * @brief API for a Time Value object used by a BACnet device object + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_TIME_VALUE_OBJECT_H +#define BACNET_TIME_VALUE_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +/** + * @brief Callback for gateway write present value request + * @param object_instance - object-instance number of the object + * @param old_value - BACNET_TIME value prior to write + * @param value - BACNET_TIME value of the write + */ +typedef void (*time_value_write_present_value_callback)(uint32_t object_instance, + BACNET_TIME *old_value, + BACNET_TIME *value); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Time_Value_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Time_Value_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Time_Value_Count(void); +BACNET_STACK_EXPORT +uint32_t Time_Value_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Time_Value_Instance_To_Index(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Time_Value_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Time_Value_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +int Time_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); + +BACNET_STACK_EXPORT +bool Time_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +BACNET_STACK_EXPORT +bool Time_Value_Present_Value_Set(uint32_t object_instance, BACNET_TIME *value); +BACNET_STACK_EXPORT +bool Time_Value_Present_Value(uint32_t object_instance, BACNET_TIME *value); +BACNET_STACK_EXPORT +void Time_Value_Write_Present_Value_Callback_Set( + time_value_write_present_value_callback cb); + +BACNET_STACK_EXPORT +uint8_t Time_Value_Status_Flags(uint32_t object_instance); + +BACNET_STACK_EXPORT +bool Time_Value_Out_Of_Service(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Time_Value_Out_Of_Service_Set(uint32_t object_instance, bool oos_flag); + +BACNET_STACK_EXPORT +char *Time_Description(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Time_Value_Description_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +bool Time_Value_Write_Enabled(uint32_t instance); +BACNET_STACK_EXPORT +void Time_Value_Write_Enable(uint32_t instance); +BACNET_STACK_EXPORT +void Time_Value_Write_Disable(uint32_t instance); + +BACNET_STACK_EXPORT +uint32_t Time_Value_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Time_Value_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Time_Value_Cleanup(void); +BACNET_STACK_EXPORT +void Time_Value_Init(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/calendar_entry.c b/src/bacnet/calendar_entry.c index 0c89a0f1..5de3787b 100644 --- a/src/bacnet/calendar_entry.c +++ b/src/bacnet/calendar_entry.c @@ -113,6 +113,10 @@ int bacnet_calendar_entry_decode( if (!apdu || !entry) { return BACNET_STATUS_REJECT; } + if (apdu_size == 0) { + /* empty list */ + return 0; + } len = bacnet_tag_decode(&apdu[apdu_len], apdu_size - apdu_len, &tag); if (len <= 0) { return BACNET_STATUS_REJECT; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5d4de053..002ea864 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -119,6 +119,7 @@ list(APPEND testdirs bacnet/basic/object/bo bacnet/basic/object/bv bacnet/basic/object/channel + bacnet/basic/object/calendar bacnet/basic/object/color_object bacnet/basic/object/color_temperature bacnet/basic/object/command @@ -138,6 +139,7 @@ list(APPEND testdirs bacnet/basic/object/osv bacnet/basic/object/piv bacnet/basic/object/schedule + bacnet/basic/object/time_value bacnet/basic/object/trendlog # basic/sys bacnet/basic/sys/color_rgb diff --git a/test/bacnet/basic/object/blo/src/main.c b/test/bacnet/basic/object/blo/src/main.c index 93b6fc50..9de91912 100644 --- a/test/bacnet/basic/object/blo/src/main.c +++ b/test/bacnet/basic/object/blo/src/main.c @@ -27,17 +27,19 @@ static void testBinaryLightingOutput(void) uint8_t apdu[MAX_APDU] = { 0 }; int len = 0, test_len = 0; BACNET_READ_PROPERTY_DATA rpdata; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; BACNET_APPLICATION_DATA_VALUE value = { 0 }; const int *pRequired = NULL; const int *pOptional = NULL; const int *pProprietary = NULL; const uint32_t instance = 123; - BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + uint32_t test_instance = 0; bool status = false; unsigned index; Binary_Lighting_Output_Init(); - Binary_Lighting_Output_Create(instance); + test_instance = Binary_Lighting_Output_Create(instance); + zassert_equal(test_instance, instance, NULL); status = Binary_Lighting_Output_Valid_Instance(instance); zassert_true(status, NULL); index = Binary_Lighting_Output_Instance_To_Index(instance); diff --git a/test/bacnet/basic/object/calendar/CMakeLists.txt b/test/bacnet/basic/object/calendar/CMakeLists.txt new file mode 100644 index 00000000..fdefad68 --- /dev/null +++ b/test/bacnet/basic/object/calendar/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}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/object/calendar.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/basic/sys/keylist.c + ${SRC_DIR}/bacnet/cov.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/memcopy.c + ${SRC_DIR}/bacnet/wp.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/calendar_entry.c + ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + # Test and test library files + ./src/main.c + ./stubs.c + ../mock/device_mock.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/object/calendar/src/main.c b/test/bacnet/basic/object/calendar/src/main.c new file mode 100644 index 00000000..e1dbddac --- /dev/null +++ b/test/bacnet/basic/object/calendar/src/main.c @@ -0,0 +1,239 @@ +/** + * @file + * @brief Unit test for BACnet Calendar object encode/decode APIs + * @author Mikhail Antropov + * @author Steve Karg + * @date June 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test Calendar handling + */ +static void testCalendar(void) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0, test_len = 0; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = {0}; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + const uint32_t instance = 1; + uint32_t test_instance = 0; + bool status = false; + unsigned index; + + Calendar_Init(); + test_instance = Calendar_Create(instance); + zassert_equal(test_instance, instance, NULL); + status = Calendar_Valid_Instance(instance); + zassert_true(status, NULL); + index = Calendar_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_CALENDAR; + rpdata.object_instance = instance; + rpdata.array_index = BACNET_ARRAY_ALL; + + Calendar_Property_Lists(&pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Calendar_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len >= 0) { + test_len = bacapp_decode_known_property(rpdata.application_data, + len, &value, rpdata.object_type, rpdata.object_property); + zassert_equal(len, test_len, + "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Calendar_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pRequired++; + } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Calendar_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data(rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Calendar_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + /* check for unsupported property - use ALL */ + rpdata.object_property = PROP_ALL; + len = Calendar_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + status = Calendar_Write_Property(&wpdata); + zassert_false(status, NULL); + /* check the delete function */ + status = Calendar_Delete(instance); + zassert_true(status, NULL); +} + +static void testPresentValue(void) +{ + const uint32_t instance = 1; + BACNET_DATE date; + BACNET_TIME time; + BACNET_CALENDAR_ENTRY entry; + BACNET_CALENDAR_ENTRY *value; + uint32_t test_instance = 0; + + Calendar_Init(); + test_instance = Calendar_Create(instance); + zassert_equal(test_instance, instance, NULL); + + datetime_local(&date, &time, NULL, NULL); + + // test Date + zassert_false(Calendar_Present_Value(instance), NULL); + zassert_equal(0, Calendar_Date_List_Count(instance), NULL); + + entry.tag = BACNET_CALENDAR_DATE; + entry.type.Date = date; + entry.type.Date.day++; + Calendar_Date_List_Add(instance, &entry); + zassert_equal(1, Calendar_Date_List_Count(instance), NULL); + zassert_false(Calendar_Present_Value(instance), NULL); + + entry.type.Date = date; + Calendar_Date_List_Add(instance, &entry); + zassert_equal(2, Calendar_Date_List_Count(instance), NULL); + zassert_true(Calendar_Present_Value(instance), NULL); + + value = Calendar_Date_List_Get(instance, 1); + value->type.Date.day += 2; + zassert_equal(2, Calendar_Date_List_Count(instance), NULL); + zassert_false(Calendar_Present_Value(instance), NULL); + + // test Date Range + entry.tag = BACNET_CALENDAR_DATE_RANGE; + entry.type.DateRange.startdate = date; + entry.type.DateRange.enddate = date; + entry.type.DateRange.startdate.day += 2; + entry.type.DateRange.enddate.day += 10; + Calendar_Date_List_Add(instance, &entry); + zassert_equal(3, Calendar_Date_List_Count(instance), NULL); + zassert_false(Calendar_Present_Value(instance), NULL); + + value = Calendar_Date_List_Get(instance, 2); + value->type.DateRange.startdate.day = date.day; + zassert_true(Calendar_Present_Value(instance), NULL); + + if (date.day > 1) { + value->type.DateRange.startdate.day --; + value->type.DateRange.enddate.day = date.day; + zassert_true(Calendar_Present_Value(instance), NULL); + } + + value->type.DateRange.startdate.day = date.day + 2; + value->type.DateRange.enddate.day = date.day + 2; + zassert_false(Calendar_Present_Value(instance), NULL); + + // test WeekNDay + entry.tag = BACNET_CALENDAR_WEEK_N_DAY; + entry.type.WeekNDay.month = 0xff; + entry.type.WeekNDay.weekofmonth = 0xff; + entry.type.WeekNDay.dayofweek = 0xff; + Calendar_Date_List_Add(instance, &entry); + zassert_equal(4, Calendar_Date_List_Count(instance), NULL); + value = Calendar_Date_List_Get(instance, 3); + zassert_true(Calendar_Present_Value(instance), NULL); + + value->type.WeekNDay.month = date.month; + zassert_true(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.month++; + zassert_false(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.month = (date.month % 2) ? 13 : 14; + zassert_true(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.month = (date.month % 2) ? 14 : 13; + zassert_false(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.month = 0xff; + + value->type.WeekNDay.weekofmonth = (date.day - 1) % 7 + 1; + zassert_true(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.weekofmonth++; + if (value->type.WeekNDay.weekofmonth >5) + value->type.WeekNDay.weekofmonth = 1; + zassert_false(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.weekofmonth = 0xff; + + value->type.WeekNDay.dayofweek = date.wday; + zassert_true(Calendar_Present_Value(instance), NULL); + value->type.WeekNDay.dayofweek++; + if (value->type.WeekNDay.dayofweek > 7) + value->type.WeekNDay.dayofweek = 1; + zassert_false(Calendar_Present_Value(instance), NULL); + + Calendar_Date_List_Delete_All(instance); + zassert_equal(0, Calendar_Date_List_Count(instance), NULL); + + zassert_true(Calendar_Delete(instance), NULL); +} + +/** + * @} + */ + +void test_main(void) +{ + ztest_test_suite(calendar_tests, + ztest_unit_test(testCalendar), + ztest_unit_test(testPresentValue) + ); + + ztest_run_test_suite(calendar_tests); +} diff --git a/test/bacnet/basic/object/calendar/stubs.c b/test/bacnet/basic/object/calendar/stubs.c new file mode 100644 index 00000000..b213a96e --- /dev/null +++ b/test/bacnet/basic/object/calendar/stubs.c @@ -0,0 +1,28 @@ +/** + * @file + * @brief Stub functions for unit test of a BACnet object + * @author Steve Karg + * @date December 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include "bacnet/datetime.h" + +bool datetime_local( + BACNET_DATE * bdate, + BACNET_TIME * btime, + int16_t * utc_offset_minutes, + bool * dst_active) +{ + bdate->year = 2023; + bdate->month = 6; + bdate->day = 26; + bdate->wday = 1; + + (void)btime; + (void)utc_offset_minutes; + (void)dst_active; + return true; +} diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index 13c9fc08..13a6e6b5 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/blo.c ${SRC_DIR}/bacnet/basic/object/bo.c ${SRC_DIR}/bacnet/basic/object/bv.c + ${SRC_DIR}/bacnet/basic/object/calendar.c ${SRC_DIR}/bacnet/basic/object/channel.c ${SRC_DIR}/bacnet/basic/object/color_object.c ${SRC_DIR}/bacnet/basic/object/color_temperature.c @@ -71,6 +72,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/time_value.c ${SRC_DIR}/bacnet/basic/object/trendlog.c ${SRC_DIR}/bacnet/basic/service/h_apdu.c ${SRC_DIR}/bacnet/basic/service/h_cov.c diff --git a/test/bacnet/basic/object/time_value/CMakeLists.txt b/test/bacnet/basic/object/time_value/CMakeLists.txt new file mode 100644 index 00000000..4bf37c3c --- /dev/null +++ b/test/bacnet/basic/object/time_value/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}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/object/time_value.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/cov.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/keylist.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/memcopy.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 + ./stubs.c + ../mock/device_mock.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/object/time_value/src/main.c b/test/bacnet/basic/object/time_value/src/main.c new file mode 100644 index 00000000..740a55e4 --- /dev/null +++ b/test/bacnet/basic/object/time_value/src/main.c @@ -0,0 +1,141 @@ +/** + * @file + * @brief Unit test for BACnet Time Value object encode/decode APIs + * @author Mikhail Antropov + * @author Steve Karg + * @date June 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test Time Value handling + */ +static void testTimeValue(void) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0, test_len = 0; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = {0}; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + const uint32_t instance = 123; + uint32_t test_instance = 0; + bool status = false; + unsigned index; + + Time_Value_Init(); + test_instance = Time_Value_Create(instance); + zassert_equal(test_instance, instance, NULL); + status = Time_Value_Valid_Instance(instance); + zassert_true(status, NULL); + index = Time_Value_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_TIME_VALUE; + rpdata.object_instance = instance; + rpdata.array_index = BACNET_ARRAY_ALL; + + Time_Value_Property_Lists( + &pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Time_Value_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len >= 0) { + test_len = bacapp_decode_known_property(rpdata.application_data, + len, &value, rpdata.object_type, rpdata.object_property); + if (rpdata.object_property != PROP_PRIORITY_ARRAY) { + zassert_equal(len, test_len, + "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + } + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Time_Value_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pRequired++; + } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Time_Value_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data(rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Time_Value_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + /* check for unsupported property - use ALL */ + rpdata.object_property = PROP_ALL; + len = Time_Value_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + status = Time_Value_Write_Property(&wpdata); + zassert_false(status, NULL); + /* check the delete function */ + status = Time_Value_Delete(instance); + zassert_true(status, NULL); + + return; +} +/** + * @} + */ + +void test_main(void) +{ + ztest_test_suite(tv_tests, + ztest_unit_test(testTimeValue) + ); + + ztest_run_test_suite(tv_tests); +} diff --git a/test/bacnet/basic/object/time_value/stubs.c b/test/bacnet/basic/object/time_value/stubs.c new file mode 100644 index 00000000..b213a96e --- /dev/null +++ b/test/bacnet/basic/object/time_value/stubs.c @@ -0,0 +1,28 @@ +/** + * @file + * @brief Stub functions for unit test of a BACnet object + * @author Steve Karg + * @date December 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include "bacnet/datetime.h" + +bool datetime_local( + BACNET_DATE * bdate, + BACNET_TIME * btime, + int16_t * utc_offset_minutes, + bool * dst_active) +{ + bdate->year = 2023; + bdate->month = 6; + bdate->day = 26; + bdate->wday = 1; + + (void)btime; + (void)utc_offset_minutes; + (void)dst_active; + return true; +} diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt index 07542d85..bc7b0bed 100644 --- a/test/bacnet/create_object/CMakeLists.txt +++ b/test/bacnet/create_object/CMakeLists.txt @@ -45,7 +45,6 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bacstr.c ${SRC_DIR}/bacnet/bactext.c ${SRC_DIR}/bacnet/basic/sys/bigend.c - ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/indtext.c diff --git a/test/bacnet/datetime/src/main.c b/test/bacnet/datetime/src/main.c index 430b6e57..589e375d 100644 --- a/test/bacnet/datetime/src/main.c +++ b/test/bacnet/datetime/src/main.c @@ -634,8 +634,15 @@ ZTEST_SUITE(datetime_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { - ztest_test_suite(datetime_tests, ztest_unit_test(testBACnetDate), - ztest_unit_test(testBACnetTime), ztest_unit_test(testBACnetDateTime), +#if 0 + ztest_unit_test(testDateEpoch), + ztest_unit_test(testBACnetDateTimeSeconds), + ztest_unit_test(testDayOfYear), +#endif + ztest_test_suite(datetime_tests, + ztest_unit_test(testBACnetDate), + ztest_unit_test(testBACnetTime), + ztest_unit_test(testBACnetDateTime), ztest_unit_test(testBACnetDayOfWeek), ztest_unit_test(testDateEpochConversion), ztest_unit_test(testBACnetDateTimeAdd), diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index d01c5005..0a1242d2 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -112,6 +112,7 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/object/bi.h ${BACNETSTACK_SRC}/bacnet/basic/object/bo.h ${BACNETSTACK_SRC}/bacnet/basic/object/bv.h + ${BACNETSTACK_SRC}/bacnet/basic/object/calendar.h ${BACNETSTACK_SRC}/bacnet/basic/object/channel.h ${BACNETSTACK_SRC}/bacnet/basic/object/color_object.h ${BACNETSTACK_SRC}/bacnet/basic/object/color_temperature.h @@ -132,6 +133,7 @@ set(BACNETSTACK_SRCS ${BACNETSTACK_SRC}/bacnet/basic/object/osv.h ${BACNETSTACK_SRC}/bacnet/basic/object/piv.h ${BACNETSTACK_SRC}/bacnet/basic/object/schedule.h + ${BACNETSTACK_SRC}/bacnet/basic/object/time_value.h ${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.h ${BACNETSTACK_SRC}/bacnet/basic/service/h_apdu.c @@ -342,6 +344,7 @@ set(BACNETSTACK_BASIC_SRCS ${BACNETSTACK_SRC}/bacnet/basic/object/bi.c ${BACNETSTACK_SRC}/bacnet/basic/object/bo.c ${BACNETSTACK_SRC}/bacnet/basic/object/bv.c + ${BACNETSTACK_SRC}/bacnet/basic/object/calendar.c ${BACNETSTACK_SRC}/bacnet/basic/object/channel.c #${BACNETSTACK_SRC}/bacnet/basic/object/client/device-client.c ${BACNETSTACK_SRC}/bacnet/basic/object/command.c @@ -364,6 +367,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/time_value.c ${BACNETSTACK_SRC}/bacnet/basic/object/trendlog.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_alarm_ack.c ${BACNETSTACK_SRC}/bacnet/basic/service/h_arf_a.c diff --git a/zephyr/subsys/object/CMakeLists.txt b/zephyr/subsys/object/CMakeLists.txt index 6a2c02b3..7561cc9c 100644 --- a/zephyr/subsys/object/CMakeLists.txt +++ b/zephyr/subsys/object/CMakeLists.txt @@ -27,6 +27,7 @@ if(CONFIG_BACNET_USE_DYNAMIC_DESCRIPTION) bi.c bo.c bv.c + calendar.c channel.c command.c credential_data_input.c @@ -42,6 +43,7 @@ if(CONFIG_BACNET_USE_DYNAMIC_DESCRIPTION) osv.c piv.c schedule.c + time_value.c trendlog.c ) diff --git a/zephyr/tests/bacnet/basic/object/calendar/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/calendar/CMakeLists.txt new file mode 100644 index 00000000..3a2b13e6 --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/calendar/CMakeLists.txt @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.13.1) + +# Extract module path and names +string(REGEX REPLACE + "/zephyr/tests/[a-zA-Z_/-]*$" "" + BACNET_BASE + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/zephyr/tests/" "/src/" + BACNET_SRC_PATH + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/zephyr/tests/" "/test/" + BACNET_TEST_PATH + ${CMAKE_CURRENT_SOURCE_DIR}) +get_filename_component(BACNET_NAME ${BACNET_BASE} NAME) + +# Update include path for this module +list(APPEND BACNET_INCLUDE ${BACNET_BASE}/src) + +if(BOARD STREQUAL unit_testing) + file(RELATIVE_PATH BACNET_INCLUDE $ENV{ZEPHYR_BASE} ${BACNET_BASE}/src) + list(APPEND INCLUDE ${BACNET_INCLUDE}) + list(APPEND SOURCES + ${BACNET_SRC_PATH}.c + ${BACNET_TEST_PATH}/src/main.c + ${BACNET_TEST_PATH}/stubs.c + ${BACNET_TEST_PATH}/../mock/device_mock.c + ) + + get_filename_component(BACNET_OBJECT_SRC ${BACNET_SRC_PATH} PATH) + get_filename_component(BACNET_BASIC_SRC ${BACNET_OBJECT_SRC} PATH) + get_filename_component(BACNET_SRC ${BACNET_BASIC_SRC} PATH) + list(APPEND SOURCES + ${BACNET_SRC}/bacaddr.c + ${BACNET_SRC}/bacapp.c + ${BACNET_SRC}/bacdcode.c + ${BACNET_SRC}/bacdest.c + ${BACNET_SRC}/bacstr.c + ${BACNET_SRC}/bacint.c + ${BACNET_SRC}/bacreal.c + ${BACNET_SRC}/datetime.c + ${BACNET_SRC}/timestamp.c + ${BACNET_SRC}/basic/sys/days.c + ${BACNET_SRC}/basic/sys/keylist.c + ${BACNET_SRC}/bacdevobjpropref.c + ${BACNET_SRC}/bactext.c + ${BACNET_SRC}/indtext.c + ${BACNET_SRC}/lighting.c + ${BACNET_SRC}/proplist.c + ${BACNET_SRC}/wp.c + ${BACNET_SRC}/hostnport.c + ${BACNET_SRC}/calendar_entry.c + ${BACNET_SRC}/dailyschedule.c + ${BACNET_SRC}/special_event.c + ${BACNET_SRC}/weeklyschedule.c + ${BACNET_SRC}/basic/sys/bigend.c + ${BACNET_SRC}/bactimevalue.c + ) + + set(CONF_FILE "${CONF_FILE};prj.unit_testing.conf") + find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(${BACNET_NAME}) +else() + find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(${BACNET_NAME}) + + target_include_directories(app PRIVATE ${BACNET_INCLUDE}) + target_sources(app PRIVATE + ${BACNET_TEST_PATH}/src/main.c + ) +endif() diff --git a/zephyr/tests/bacnet/basic/object/calendar/prj.conf b/zephyr/tests/bacnet/basic/object/calendar/prj.conf new file mode 100644 index 00000000..7c43f2ec --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/calendar/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_BACNETSTACK=y diff --git a/zephyr/tests/bacnet/basic/object/calendar/prj.unit_testing.conf b/zephyr/tests/bacnet/basic/object/calendar/prj.unit_testing.conf new file mode 100644 index 00000000..511170ca --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/calendar/prj.unit_testing.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +#CONFIG_BACNETSTACK=y diff --git a/zephyr/tests/bacnet/basic/object/calendar/testcase.yaml b/zephyr/tests/bacnet/basic/object/calendar/testcase.yaml new file mode 100644 index 00000000..3ea74aed --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/calendar/testcase.yaml @@ -0,0 +1,6 @@ +tests: + bacnet.basic.object.calendar: + tags: bacnet + bacnet.basic.object.calendar.unit: + tags: bacnet + type: unit diff --git a/zephyr/tests/bacnet/basic/object/device/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/device/CMakeLists.txt index 50051e41..b5e1f890 100644 --- a/zephyr/tests/bacnet/basic/object/device/CMakeLists.txt +++ b/zephyr/tests/bacnet/basic/object/device/CMakeLists.txt @@ -40,6 +40,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/bacstr.c ${BACNET_SRC}/bacint.c ${BACNET_SRC}/bacreal.c + ${BACNET_SRC}/calendar.c ${BACNET_SRC}/datetime.c ${BACNET_SRC}/timestamp.c ${BACNET_SRC}/basic/sys/days.c @@ -68,6 +69,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/basic/object/bi.c ${BACNET_SRC}/basic/object/bo.c ${BACNET_SRC}/basic/object/bv.c + ${BACNET_SRC}/basic/object/calendar.c ${BACNET_SRC}/basic/object/channel.c ${BACNET_SRC}/basic/object/color_object.c ${BACNET_SRC}/basic/object/color_temperature.c @@ -84,6 +86,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/basic/object/osv.c ${BACNET_SRC}/basic/object/piv.c ${BACNET_SRC}/basic/object/schedule.c + ${BACNET_SRC}/basic/object/time_value.c ${BACNET_SRC}/basic/object/trendlog.c ${BACNET_SRC}/hostnport.c ${BACNET_SRC}/basic/service/h_apdu.c diff --git a/zephyr/tests/bacnet/basic/object/time_value/CMakeLists.txt b/zephyr/tests/bacnet/basic/object/time_value/CMakeLists.txt new file mode 100644 index 00000000..3a2b13e6 --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/time_value/CMakeLists.txt @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.13.1) + +# Extract module path and names +string(REGEX REPLACE + "/zephyr/tests/[a-zA-Z_/-]*$" "" + BACNET_BASE + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/zephyr/tests/" "/src/" + BACNET_SRC_PATH + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/zephyr/tests/" "/test/" + BACNET_TEST_PATH + ${CMAKE_CURRENT_SOURCE_DIR}) +get_filename_component(BACNET_NAME ${BACNET_BASE} NAME) + +# Update include path for this module +list(APPEND BACNET_INCLUDE ${BACNET_BASE}/src) + +if(BOARD STREQUAL unit_testing) + file(RELATIVE_PATH BACNET_INCLUDE $ENV{ZEPHYR_BASE} ${BACNET_BASE}/src) + list(APPEND INCLUDE ${BACNET_INCLUDE}) + list(APPEND SOURCES + ${BACNET_SRC_PATH}.c + ${BACNET_TEST_PATH}/src/main.c + ${BACNET_TEST_PATH}/stubs.c + ${BACNET_TEST_PATH}/../mock/device_mock.c + ) + + get_filename_component(BACNET_OBJECT_SRC ${BACNET_SRC_PATH} PATH) + get_filename_component(BACNET_BASIC_SRC ${BACNET_OBJECT_SRC} PATH) + get_filename_component(BACNET_SRC ${BACNET_BASIC_SRC} PATH) + list(APPEND SOURCES + ${BACNET_SRC}/bacaddr.c + ${BACNET_SRC}/bacapp.c + ${BACNET_SRC}/bacdcode.c + ${BACNET_SRC}/bacdest.c + ${BACNET_SRC}/bacstr.c + ${BACNET_SRC}/bacint.c + ${BACNET_SRC}/bacreal.c + ${BACNET_SRC}/datetime.c + ${BACNET_SRC}/timestamp.c + ${BACNET_SRC}/basic/sys/days.c + ${BACNET_SRC}/basic/sys/keylist.c + ${BACNET_SRC}/bacdevobjpropref.c + ${BACNET_SRC}/bactext.c + ${BACNET_SRC}/indtext.c + ${BACNET_SRC}/lighting.c + ${BACNET_SRC}/proplist.c + ${BACNET_SRC}/wp.c + ${BACNET_SRC}/hostnport.c + ${BACNET_SRC}/calendar_entry.c + ${BACNET_SRC}/dailyschedule.c + ${BACNET_SRC}/special_event.c + ${BACNET_SRC}/weeklyschedule.c + ${BACNET_SRC}/basic/sys/bigend.c + ${BACNET_SRC}/bactimevalue.c + ) + + set(CONF_FILE "${CONF_FILE};prj.unit_testing.conf") + find_package(Zephyr COMPONENTS unittest REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(${BACNET_NAME}) +else() + find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + project(${BACNET_NAME}) + + target_include_directories(app PRIVATE ${BACNET_INCLUDE}) + target_sources(app PRIVATE + ${BACNET_TEST_PATH}/src/main.c + ) +endif() diff --git a/zephyr/tests/bacnet/basic/object/time_value/prj.conf b/zephyr/tests/bacnet/basic/object/time_value/prj.conf new file mode 100644 index 00000000..7c43f2ec --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/time_value/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_BACNETSTACK=y diff --git a/zephyr/tests/bacnet/basic/object/time_value/prj.unit_testing.conf b/zephyr/tests/bacnet/basic/object/time_value/prj.unit_testing.conf new file mode 100644 index 00000000..511170ca --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/time_value/prj.unit_testing.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +#CONFIG_BACNETSTACK=y diff --git a/zephyr/tests/bacnet/basic/object/time_value/testcase.yaml b/zephyr/tests/bacnet/basic/object/time_value/testcase.yaml new file mode 100644 index 00000000..6255dfaf --- /dev/null +++ b/zephyr/tests/bacnet/basic/object/time_value/testcase.yaml @@ -0,0 +1,6 @@ +tests: + bacnet.basic.object.time_value: + tags: bacnet + bacnet.basic.object.time_value.unit: + tags: bacnet + type: unit diff --git a/zephyr/tests/bacnet/event/CMakeLists.txt b/zephyr/tests/bacnet/event/CMakeLists.txt index cd1a3db7..0e7156fc 100644 --- a/zephyr/tests/bacnet/event/CMakeLists.txt +++ b/zephyr/tests/bacnet/event/CMakeLists.txt @@ -53,7 +53,7 @@ if(BOARD STREQUAL unit_testing) ${BACNET_SRC}/dailyschedule.c ${BACNET_SRC}/lighting.c ${BACNET_SRC}/timestamp.c - ${BACNET_SRC}/hostnport.c + ${BACNET_SRC}/hostnport.c ) set(CONF_FILE "${CONF_FILE};prj.unit_testing.conf")