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")