diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e0c3285..62146f7f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,8 @@ The git repositories are hosted at the following sites:
### Added
+* Added a basic timer object type example. (#1123)
+* Added BACnetLIST utility for handling WriteProperty to a list. (#1123)
* Added Device_Object_Functions() API to return basic object API table
of functions for all objects. Added Device_Object_Functions_Find() API
to enable override of basic object API function. (#1115)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 832058b1..489bb107 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -412,6 +412,8 @@ add_library(${PROJECT_NAME}
src/bacnet/basic/object/structured_view.h
src/bacnet/basic/object/time_value.c
src/bacnet/basic/object/time_value.h
+ src/bacnet/basic/object/timer.c
+ src/bacnet/basic/object/timer.h
src/bacnet/basic/object/trendlog.c
src/bacnet/basic/object/trendlog.h
src/bacnet/basic/service/h_alarm_ack.c
@@ -663,6 +665,8 @@ add_library(${PROJECT_NAME}
src/bacnet/rp.h
src/bacnet/rpm.c
src/bacnet/rpm.h
+ src/bacnet/timer_value.c
+ src/bacnet/timer_value.h
src/bacnet/timestamp.c
src/bacnet/timestamp.h
src/bacnet/timesync.c
diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile
index 4c9e73e7..8f5dc063 100644
--- a/apps/gateway/Makefile
+++ b/apps/gateway/Makefile
@@ -40,6 +40,7 @@ BACNET_OBJECT_SRC := \
$(BACNET_OBJECT_DIR)/nc.c \
$(BACNET_OBJECT_DIR)/netport.c \
$(BACNET_OBJECT_DIR)/time_value.c \
+ $(BACNET_OBJECT_DIR)/timer.c \
$(BACNET_OBJECT_DIR)/trendlog.c \
$(BACNET_OBJECT_DIR)/schedule.c \
$(BACNET_OBJECT_DIR)/structured_view.c \
diff --git a/apps/server-basic/Makefile b/apps/server-basic/Makefile
index 1324e358..76b6c8d3 100644
--- a/apps/server-basic/Makefile
+++ b/apps/server-basic/Makefile
@@ -40,6 +40,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/osv.c \
$(BACNET_OBJECT_DIR)/program.c \
$(BACNET_OBJECT_DIR)/structured_view.c \
+ $(BACNET_OBJECT_DIR)/timer.c \
$(BACNET_OBJECT_DIR)/time_value.c
# TARGET_EXT is defined in apps/Makefile as .exe or nothing
diff --git a/apps/server-mini/Makefile b/apps/server-mini/Makefile
index 01b7692a..e8423b71 100644
--- a/apps/server-mini/Makefile
+++ b/apps/server-mini/Makefile
@@ -37,6 +37,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/netport.c \
$(BACNET_OBJECT_DIR)/program.c \
$(BACNET_OBJECT_DIR)/time_value.c \
+ $(BACNET_OBJECT_DIR)/timer.c \
$(BACNET_OBJECT_DIR)/trendlog.c \
$(BACNET_OBJECT_DIR)/schedule.c \
$(BACNET_OBJECT_DIR)/structured_view.c \
diff --git a/apps/server/Makefile b/apps/server/Makefile
index 436021d5..63c47b34 100644
--- a/apps/server/Makefile
+++ b/apps/server/Makefile
@@ -34,6 +34,7 @@ SRC = main.c \
$(BACNET_OBJECT_DIR)/nc.c \
$(BACNET_OBJECT_DIR)/netport.c \
$(BACNET_OBJECT_DIR)/time_value.c \
+ $(BACNET_OBJECT_DIR)/timer.c \
$(BACNET_OBJECT_DIR)/trendlog.c \
$(BACNET_OBJECT_DIR)/schedule.c \
$(BACNET_OBJECT_DIR)/structured_view.c \
diff --git a/doc/htdocs/index.html b/doc/htdocs/index.html
index 4dde5e2c..49f84601 100644
--- a/doc/htdocs/index.html
+++ b/doc/htdocs/index.html
@@ -958,6 +958,13 @@
Yes |
+
+ |
+ Timer |
+ Yes |
+
|
diff --git a/ports/at91sam7s/CMakeLists.txt b/ports/at91sam7s/CMakeLists.txt
index 2e4fb391..3d423229 100644
--- a/ports/at91sam7s/CMakeLists.txt
+++ b/ports/at91sam7s/CMakeLists.txt
@@ -168,6 +168,7 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_CORE}/reject.c
${LIBRARY_BACNET_CORE}/rp.c
${LIBRARY_BACNET_CORE}/rpm.c
+ ${LIBRARY_BACNET_CORE}/timer_value.c
${LIBRARY_BACNET_CORE}/timestamp.c
${LIBRARY_BACNET_CORE}/weeklyschedule.c
${LIBRARY_BACNET_CORE}/dailyschedule.c
diff --git a/ports/at91sam7s/Makefile b/ports/at91sam7s/Makefile
index 25f45612..1d377cb2 100644
--- a/ports/at91sam7s/Makefile
+++ b/ports/at91sam7s/Makefile
@@ -130,6 +130,7 @@ CORESRC = $(BACNET_CORE)/abort.c \
$(BACNET_CORE)/reject.c \
$(BACNET_CORE)/rp.c \
$(BACNET_CORE)/rpm.c \
+ $(BACNET_CORE)/timer_value.c \
$(BACNET_CORE)/timestamp.c \
$(BACNET_CORE)/weeklyschedule.c \
$(BACNET_CORE)/dailyschedule.c \
diff --git a/ports/at91sam7s/bacnet.ewp b/ports/at91sam7s/bacnet.ewp
index ab1f4f9c..cdaba4af 100644
--- a/ports/at91sam7s/bacnet.ewp
+++ b/ports/at91sam7s/bacnet.ewp
@@ -1188,6 +1188,9 @@
$PROJ_DIR$\..\..\src\bacnet\rpm.c
+
+ $PROJ_DIR$\..\..\src\bacnet\timer_value.c
+
$PROJ_DIR$\..\..\src\bacnet\timestamp.c
diff --git a/ports/stm32f10x/CMakeLists.txt b/ports/stm32f10x/CMakeLists.txt
index 29512a26..f406f2ff 100644
--- a/ports/stm32f10x/CMakeLists.txt
+++ b/ports/stm32f10x/CMakeLists.txt
@@ -194,6 +194,7 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_CORE}/reject.c
${LIBRARY_BACNET_CORE}/rp.c
${LIBRARY_BACNET_CORE}/rpm.c
+ ${LIBRARY_BACNET_CORE}/timer_value.c
${LIBRARY_BACNET_CORE}/timestamp.c
${LIBRARY_BACNET_CORE}/weeklyschedule.c
${LIBRARY_BACNET_CORE}/dailyschedule.c
diff --git a/ports/stm32f10x/Makefile b/ports/stm32f10x/Makefile
index 897e645e..9c0f0b29 100644
--- a/ports/stm32f10x/Makefile
+++ b/ports/stm32f10x/Makefile
@@ -82,6 +82,7 @@ BACNET_SRC = \
$(BACNET_CORE)/rp.c \
$(BACNET_CORE)/rpm.c \
$(BACNET_CORE)/special_event.c \
+ $(BACNET_CORE)/timer_value.c \
$(BACNET_CORE)/timestamp.c \
$(BACNET_CORE)/weeklyschedule.c \
$(BACNET_CORE)/whohas.c \
diff --git a/ports/stm32f10x/bacnet.ewp b/ports/stm32f10x/bacnet.ewp
index ec3d0a8b..3b61ed78 100644
--- a/ports/stm32f10x/bacnet.ewp
+++ b/ports/stm32f10x/bacnet.ewp
@@ -1118,6 +1118,9 @@
$PROJ_DIR$\..\..\src\bacnet\rpm.c
+
+ $PROJ_DIR$\..\..\src\bacnet\timer_value.c
+
$PROJ_DIR$\..\..\src\bacnet\timestamp.c
diff --git a/ports/stm32f4xx/CMakeLists.txt b/ports/stm32f4xx/CMakeLists.txt
index 1ba66bf3..be71c80a 100644
--- a/ports/stm32f4xx/CMakeLists.txt
+++ b/ports/stm32f4xx/CMakeLists.txt
@@ -227,6 +227,7 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_CORE}/reject.c
${LIBRARY_BACNET_CORE}/rp.c
${LIBRARY_BACNET_CORE}/rpm.c
+ ${LIBRARY_BACNET_CORE}/timer_value.c
${LIBRARY_BACNET_CORE}/timestamp.c
${LIBRARY_BACNET_CORE}/timesync.c
${LIBRARY_BACNET_CORE}/weeklyschedule.c
diff --git a/ports/stm32f4xx/Makefile b/ports/stm32f4xx/Makefile
index d860f4fb..86feced7 100644
--- a/ports/stm32f4xx/Makefile
+++ b/ports/stm32f4xx/Makefile
@@ -104,6 +104,7 @@ BACNET_SRC = \
$(BACNET_CORE)/rp.c \
$(BACNET_CORE)/rpm.c \
$(BACNET_CORE)/special_event.c \
+ $(BACNET_CORE)/timer_value.c \
$(BACNET_CORE)/timestamp.c \
$(BACNET_CORE)/timesync.c \
$(BACNET_CORE)/weeklyschedule.c \
diff --git a/ports/stm32f4xx/bacnet.ewp b/ports/stm32f4xx/bacnet.ewp
index 30a78b31..c232a716 100644
--- a/ports/stm32f4xx/bacnet.ewp
+++ b/ports/stm32f4xx/bacnet.ewp
@@ -1127,6 +1127,9 @@
$PROJ_DIR$\..\..\src\bacnet\rpm.c
+
+ $PROJ_DIR$\..\..\src\bacnet\timer_value.c
+
$PROJ_DIR$\..\..\src\bacnet\timestamp.c
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 8e78629d..65981bf0 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
@@ -202,6 +202,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 ba5cd764..1a04927e 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
@@ -75,6 +75,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj b/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj
index fb8aec57..f6a3d187 100644
--- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj
+++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj
@@ -215,6 +215,7 @@
+
diff --git a/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj.filters b/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj.filters
index 7c8f0368..22c6b5ac 100644
--- a/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj.filters
+++ b/ports/win32/Microsoft Visual Studio 2019/BACnet_Stack_Library/BACnet_Stack_Library.vcxproj.filters
@@ -129,6 +129,9 @@
Source Files
+
+ Source Files
+
Source Files
diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj
index 57f7055d..4f338c35 100644
--- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj
+++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj
@@ -89,6 +89,7 @@
+
@@ -208,6 +209,7 @@
+
@@ -301,6 +303,7 @@
+
diff --git a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters
index 1cf9573d..d7d6c8dd 100644
--- a/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters
+++ b/ports/win32/Microsoft Visual Studio/bacnet-stack/bacnet-stack.vcxproj.filters
@@ -201,6 +201,9 @@
Source Files\src\bacnet
+
+ Source Files\src\bacnet
+
Source Files\src\bacnet
@@ -627,6 +630,9 @@
Source Files\src\bacnet\basic\object
+
+ Source Files\src\bacnet\basic\object
+
Source Files\src\bacnet\basic\service
@@ -1271,6 +1277,9 @@
Source Files\src\bacnet\basic\object
+
+ Source Files\src\bacnet\basic\object
+
Source Files\src\bacnet\basic\service
diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c
index 49080328..64a247fe 100644
--- a/src/bacnet/bacapp.c
+++ b/src/bacnet/bacapp.c
@@ -37,6 +37,7 @@
#include "bacnet/calendar_entry.h"
#include "bacnet/special_event.h"
#include "bacnet/channel_value.h"
+#include "bacnet/timer_value.h"
#include "bacnet/basic/sys/platform.h"
#if defined(BACAPP_SCALE)
@@ -528,6 +529,18 @@ int bacapp_encode_application_data(
apdu, &value->type.Channel_Value);
break;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ case BACNET_APPLICATION_TAG_TIMER_VALUE:
+ /* BACnetTimerStateChangeValue */
+ apdu_len = bacnet_timer_value_type_encode(
+ apdu, &value->type.Timer_Value);
+ break;
+#endif
+#if defined(BACAPP_NO_VALUE)
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ apdu_len = bacnet_timer_value_no_value_encode(apdu);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
/* BACnetLogRecord */
@@ -1347,7 +1360,8 @@ int bacapp_known_property_tag(
case PROP_SC_PRIMARY_HUB_CONNECTION_STATUS:
case PROP_SC_FAILOVER_HUB_CONNECTION_STATUS:
return BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS;
-
+ case PROP_STATE_CHANGE_VALUES:
+ return BACNET_APPLICATION_TAG_TIMER_VALUE;
default:
return -1;
}
@@ -1660,6 +1674,13 @@ int bacapp_decode_application_tag_value(
apdu, apdu_size, &value->type.Channel_Value);
break;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ case BACNET_APPLICATION_TAG_TIMER_VALUE:
+ /* BACnetTimerStateChangeValue */
+ apdu_len = bacnet_timer_value_decode(
+ apdu, apdu_size, &value->type.Timer_Value);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
/* BACnetLogRecord */
@@ -2440,6 +2461,22 @@ static int bacapp_snprintf_enumerated(
ret_val = bacapp_snprintf(
str, str_len, "%s", bactext_protocol_level_name(value));
break;
+ case PROP_EVENT_TYPE:
+ ret_val = bacapp_snprintf(
+ str, str_len, "%s", bactext_event_type_name(value));
+ break;
+ case PROP_NOTIFY_TYPE:
+ ret_val = bacapp_snprintf(
+ str, str_len, "%s", bactext_notify_type_name(value));
+ break;
+ case PROP_TIMER_STATE:
+ ret_val = bacapp_snprintf(
+ str, str_len, "%s", bactext_timer_state_name(value));
+ break;
+ case PROP_LAST_STATE_CHANGE:
+ ret_val = bacapp_snprintf(
+ str, str_len, "%s", bactext_timer_transition_name(value));
+ break;
default:
ret_val =
bacapp_snprintf(str, str_len, "%lu", (unsigned long)value);
@@ -3981,6 +4018,17 @@ int bacapp_snprintf_value(
str, str_len, &value->type.Channel_Value);
break;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ case BACNET_APPLICATION_TAG_TIMER_VALUE:
+ ret_val = bacnet_timer_value_to_ascii(
+ &value->type.Timer_Value, str, str_len);
+ break;
+#endif
+#if defined(BACAPP_NO_VALUE)
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ ret_val = bacnet_timer_value_no_value_to_ascii(str, str_len);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
ret_val = bacapp_snprintf_log_record(
@@ -4762,15 +4810,28 @@ bool bacapp_parse_application_data(
#endif
#if defined(BACAPP_ACCESS_RULE)
case BACNET_APPLICATION_TAG_ACCESS_RULE:
- bacnet_access_rule_from_ascii(&value->type.Access_Rule, argv);
+ status = bacnet_access_rule_from_ascii(
+ &value->type.Access_Rule, argv);
break;
#endif
#if defined(BACAPP_CHANNEL_VALUE)
case BACNET_APPLICATION_TAG_CHANNEL_VALUE:
- bacnet_channel_value_from_ascii(
+ status = bacnet_channel_value_from_ascii(
&value->type.Channel_Value, argv);
break;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ case BACNET_APPLICATION_TAG_TIMER_VALUE:
+ status = bacnet_timer_value_from_ascii(
+ &value->type.Timer_Value, argv);
+ break;
+#endif
+#if defined(BACAPP_NO_VALUE)
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ status =
+ bacnet_timer_value_no_value_from_ascii(&value->tag, argv);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
status = bacnet_log_record_datum_from_ascii(
@@ -5484,6 +5545,19 @@ bool bacapp_same_value(
&test_value->type.Channel_Value);
break;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ case BACNET_APPLICATION_TAG_TIMER_VALUE:
+ status = bacnet_timer_value_same(
+ &value->type.Timer_Value, &test_value->type.Timer_Value);
+ break;
+#endif
+#if defined(BACAPP_NO_VALUE)
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ if (value->tag == test_value->tag) {
+ status = true;
+ }
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
status = bacnet_log_record_same(
diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h
index 687a021c..193cad59 100644
--- a/src/bacnet/bacapp.h
+++ b/src/bacnet/bacapp.h
@@ -30,6 +30,7 @@
#include "bacnet/calendar_entry.h"
#include "bacnet/special_event.h"
#include "bacnet/channel_value.h"
+#include "bacnet/timer_value.h"
#include "bacnet/secure_connect.h"
#ifndef BACAPP_PRINT_ENABLED
@@ -67,7 +68,12 @@ typedef struct BACnet_Application_Data_Value {
uint8_t context_tag; /* only used for context specific data */
uint8_t tag; /* application tag data type */
union {
- /* NULL - not needed as it is encoded in the tag alone */
+#if defined(BACAPP_NULL)
+ /* no value needed because it is encoded in the tag alone */
+#endif
+#if defined(BACAPP_NO_VALUE)
+ /* no value needed because it is encoded in the tag alone */
+#endif
#if defined(BACAPP_BOOLEAN)
bool Boolean;
#endif
@@ -170,6 +176,9 @@ typedef struct BACnet_Application_Data_Value {
#if defined(BACAPP_CHANNEL_VALUE)
BACNET_CHANNEL_VALUE Channel_Value;
#endif
+#if defined(BACAPP_TIMER_VALUE)
+ BACNET_TIMER_STATE_CHANGE_VALUE Timer_Value;
+#endif
#if defined(BACAPP_LOG_RECORD)
BACNET_LOG_RECORD Log_Record;
#endif
diff --git a/src/bacnet/bacdcode.c b/src/bacnet/bacdcode.c
index 2a901a7e..a4daa335 100644
--- a/src/bacnet/bacdcode.c
+++ b/src/bacnet/bacdcode.c
@@ -1426,6 +1426,44 @@ int bacnet_null_application_decode(const uint8_t *apdu, uint32_t apdu_size)
return apdu_len;
}
+/**
+ * @brief Decode the Null Value when context encoded
+ * From clause 20.2.2 Encoding of a Null Value
+ * and 20.2.1 General Rules for Encoding BACnet Tags
+ *
+ * @param apdu - buffer to hold the bytes
+ * @param apdu_size - number of bytes in the buffer to decode
+ * @param tag_value - context tag number expected
+ *
+ * @return number of bytes decoded, zero if tag mismatch,
+ * or #BACNET_STATUS_ERROR (-1) if malformed
+ */
+int bacnet_null_context_decode(
+ const uint8_t *apdu, uint32_t apdu_size, uint8_t tag_value)
+{
+ int apdu_len = BACNET_STATUS_ERROR;
+ int len = 0;
+ BACNET_TAG tag = { 0 };
+
+ if (apdu_size == 0) {
+ return 0;
+ }
+ len = bacnet_tag_decode(apdu, apdu_size, &tag);
+ if (len > 0) {
+ if (tag.context && (tag.number == tag_value)) {
+ if (tag.len_value_type == 0) {
+ apdu_len = len;
+ } else {
+ apdu_len = BACNET_STATUS_ERROR;
+ }
+ } else {
+ apdu_len = 0;
+ }
+ }
+
+ return apdu_len;
+}
+
/**
* @brief Reverse the bits of the given byte.
*
@@ -4819,6 +4857,157 @@ int decode_context_date(
return len;
}
+/**
+ * @brief Encode a context tagged BACnetTimerStateChangeValue
+ * with a constructed value type
+ * @param apdu buffer to be encoded, or NULL for length
+ * @param tag_value - context tag number to encapsulate the value
+ * @param value The value to be encoded.
+ * @return the number of apdu bytes encoded
+ */
+int bacnet_constructed_value_context_encode(
+ uint8_t *apdu,
+ uint8_t tag_value,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value)
+{
+ int len = 0;
+ int apdu_len = 0;
+
+ if (value) {
+ len = encode_opening_tag(apdu, tag_value);
+ apdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
+ len = value->data_len;
+ if (apdu) {
+ memcpy(apdu, value->data, len);
+ }
+ apdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
+ len = encode_closing_tag(apdu, tag_value);
+ apdu_len += len;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Decodes a constructed value type of a given length
+ * @param apdu - the APDU buffer to decode
+ * @param apdu_size - number of bytes in the buffer to decode
+ * @param len_value - number of bytes in the constructed value data
+ * @param value - the value where the decoded data is copied into,
+ * or NULL for getting the length
+ * @return number of bytes decoded (0..N), or BACNET_STATUS_ERROR on error
+ */
+int bacnet_constructed_value_decode(
+ const uint8_t *apdu,
+ uint32_t apdu_size,
+ uint32_t len_value,
+ BACNET_CONSTRUCTED_VALUE_TYPE *value)
+{
+ if (len_value <= apdu_size) {
+ if (value) {
+ value->data_len = len_value;
+ memcpy(value->data, apdu, len_value);
+ }
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+
+ return (int)len_value;
+}
+
+/**
+ * @brief Decodes a context tagged constructed value type
+ * @param apdu - the APDU buffer
+ * @param apdu_size - the APDU buffer size
+ * @param tag_value - context tag number expected
+ * @param value - the value where the decoded data is copied into
+ * @return length of the APDU buffer decoded, or BACNET_STATUS_ERROR
+ */
+int bacnet_constructed_value_context_decode(
+ const uint8_t *apdu,
+ uint32_t apdu_size,
+ uint8_t tag_number,
+ BACNET_CONSTRUCTED_VALUE_TYPE *value)
+{
+ int apdu_len = 0;
+ int len, len_value;
+
+ if (!bacnet_is_opening_tag_number(
+ &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
+ return BACNET_STATUS_ERROR;
+ }
+ /* find the matching closing tag to learn the length*/
+ len_value =
+ bacnet_enclosed_data_length(&apdu[apdu_len], apdu_size - apdu_len);
+ /* now update apdu_len with the opening tag length */
+ apdu_len += len;
+ /* constructed value */
+ len = bacnet_constructed_value_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, len_value, NULL);
+ if (len < 0) {
+ return BACNET_STATUS_ERROR;
+ }
+ if (value) {
+ if (len <= sizeof(value->data)) {
+ len = bacnet_constructed_value_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, len_value, value);
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ }
+ apdu_len += len;
+ if (!bacnet_is_closing_tag_number(
+ &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
+ return BACNET_STATUS_ERROR;
+ }
+ apdu_len += len;
+
+ return apdu_len;
+}
+
+/**
+ * @brief Compare two constructed values
+ * @param value1 - the first value to compare
+ * @param value2 - the second value to compare
+ * @return true if value1 is the same as value2 up to the length of the data
+ */
+bool bacnet_constructed_value_same(
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value1,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value2)
+{
+ if (value1->data_len != value2->data_len) {
+ return false;
+ }
+ if (value1->data_len > sizeof(value1->data)) {
+ return false;
+ }
+ if (memcmp(value1->data, value2->data, value1->data_len) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Copy one constructed value to another
+ * @param dest - the destination value
+ * @param src - the source value to copy to the destination
+ * @return true if the value is copied from src to dst
+ */
+bool bacnet_constructed_value_copy(
+ BACNET_CONSTRUCTED_VALUE_TYPE *dest,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *src)
+{
+ memcpy(dest, src, sizeof(BACNET_CONSTRUCTED_VALUE_TYPE));
+ return true;
+}
+
/**
* Encode a simple ACK and returns the number of apdu bytes consumed.
*
@@ -4917,7 +5106,7 @@ int bacnet_array_encode(
* @param array_index [in] array index to be decoded
* 0 for the array size
* 1 to n for individual array members
- * BACNET_ARRAY_ALL for the full array to be read.
+ * BACNET_ARRAY_ALL for the full array to be written.
* @param decode_function [in] function to decode one property array element and
* determine the length
* @param write_function [in] function to write one property array element with
@@ -4992,3 +5181,81 @@ BACNET_ERROR_CODE bacnet_array_write(
return error_code;
}
+
+/**
+ * @brief Decode a BACnetList property value and call a function for each
+ * element
+ * @param object_instance [in] BACnet network port object instance number
+ * @param array_index [in] array index to be decoded
+ * 0 for the list size - not permitted for a list
+ * 1 to n for individual array members - not permitted for a list
+ * BACNET_ARRAY_ALL for the full list to be written
+ * @param decode_function [in] function to decode one property list element and
+ * determine the length
+ * @param add_function [in] function to add one property list element with
+ * the encoded value
+ * @param max_elements [in] number of elements in the list
+ * @param apdu [out] Buffer in which the APDU contents are built.
+ * @param max_apdu [in] Max length of the APDU buffer.
+ * @return BACNET_ERROR_CODE value
+ */
+BACNET_ERROR_CODE bacnet_list_write(
+ uint32_t object_instance,
+ BACNET_ARRAY_INDEX array_index,
+ bacnet_array_property_element_decode_function decode_function,
+ bacnet_list_property_element_add_function add_function,
+ BACNET_UNSIGNED_INTEGER array_size,
+ uint8_t *apdu,
+ size_t apdu_size)
+{
+ int len = 0;
+ BACNET_ERROR_CODE error_code = ERROR_CODE_SUCCESS;
+ size_t apdu_len;
+ BACNET_ARRAY_INDEX count = 0;
+
+ if (array_index == BACNET_ARRAY_ALL) {
+ /* verify all elements will fit in the BACnetLIST */
+ apdu_len = 0;
+ while (apdu_len < apdu_size) {
+ len = decode_function(
+ object_instance, &apdu[apdu_len], apdu_size - apdu_len);
+ if (len > 0) {
+ apdu_len += len;
+ count++;
+ } else if (len == 0) {
+ /* end of list - it fits! */
+ break;
+ } else {
+ /* bad decode */
+ error_code = ERROR_CODE_ABORT_OTHER;
+ }
+ }
+ if (error_code == ERROR_CODE_SUCCESS) {
+ if (count > array_size) {
+ /* maybe? what about duplicates? */
+ error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
+ }
+ }
+ if (error_code == ERROR_CODE_SUCCESS) {
+ /* empty the list */
+ (void)add_function(object_instance, NULL, 0);
+ /* add each unique element */
+ apdu_len = 0;
+ while (apdu_len < apdu_size) {
+ len = decode_function(
+ object_instance, &apdu[apdu_len], apdu_size - apdu_len);
+ error_code =
+ add_function(object_instance, &apdu[apdu_len], len);
+ if (error_code != ERROR_CODE_SUCCESS) {
+ break;
+ }
+ apdu_len += len;
+ }
+ }
+ } else {
+ /* array-index was specified */
+ error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ }
+
+ return error_code;
+}
diff --git a/src/bacnet/bacdcode.h b/src/bacnet/bacdcode.h
index 43e59a7e..5a5da8e4 100644
--- a/src/bacnet/bacdcode.h
+++ b/src/bacnet/bacdcode.h
@@ -57,6 +57,26 @@ typedef BACNET_ERROR_CODE (*bacnet_array_property_element_write_function)(
uint8_t *application_data,
size_t application_data_len);
+/**
+ * @brief Add a unique element to a BACnetLIST property
+ * @param object_instance [in] BACnet network port object instance number
+ * @param application_data [in] encoded element value
+ * @param application_data_len [in] The size of the encoded element value
+ * @return BACNET_ERROR_CODE value
+ */
+typedef BACNET_ERROR_CODE (*bacnet_list_property_element_add_function)(
+ uint32_t object_instance,
+ uint8_t *application_data,
+ size_t application_data_len);
+
+#ifndef BACNET_CONSTRUCTED_VALUE_SIZE
+#define BACNET_CONSTRUCTED_VALUE_SIZE MAX_APDU
+#endif
+typedef struct BACnet_Constructed_Value_Type {
+ uint8_t data[BACNET_CONSTRUCTED_VALUE_SIZE];
+ uint16_t data_len;
+} BACNET_CONSTRUCTED_VALUE_TYPE;
+
typedef struct BACnetTag {
uint8_t number;
bool application : 1;
@@ -164,6 +184,9 @@ BACNET_STACK_EXPORT
int encode_context_null(uint8_t *apdu, uint8_t tag_number);
BACNET_STACK_EXPORT
int bacnet_null_application_decode(const uint8_t *apdu, uint32_t apdu_size);
+BACNET_STACK_EXPORT
+int bacnet_null_context_decode(
+ const uint8_t *apdu, uint32_t apdu_size, uint8_t tag_value);
/* from clause 20.2.3 Encoding of a Boolean Value */
BACNET_STACK_EXPORT
@@ -618,6 +641,32 @@ int bacnet_date_context_decode(
uint8_t tag_value,
BACNET_DATE *value);
+BACNET_STACK_EXPORT
+int bacnet_constructed_value_context_encode(
+ uint8_t *apdu,
+ uint8_t tag_value,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value);
+BACNET_STACK_EXPORT
+int bacnet_constructed_value_decode(
+ const uint8_t *apdu,
+ uint32_t apdu_size,
+ uint32_t len_value,
+ BACNET_CONSTRUCTED_VALUE_TYPE *value);
+BACNET_STACK_EXPORT
+int bacnet_constructed_value_context_decode(
+ const uint8_t *apdu,
+ uint32_t apdu_size,
+ uint8_t tag_value,
+ BACNET_CONSTRUCTED_VALUE_TYPE *value);
+BACNET_STACK_EXPORT
+bool bacnet_constructed_value_same(
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value1,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *value2);
+BACNET_STACK_EXPORT
+bool bacnet_constructed_value_copy(
+ BACNET_CONSTRUCTED_VALUE_TYPE *dest,
+ const BACNET_CONSTRUCTED_VALUE_TYPE *src);
+
/* from clause 20.1.2.4 max-segments-accepted */
/* and clause 20.1.2.5 max-APDU-length-accepted */
/* returns the encoded octet */
@@ -651,6 +700,16 @@ BACNET_ERROR_CODE bacnet_array_write(
uint8_t *apdu,
size_t apdu_size);
+BACNET_STACK_EXPORT
+BACNET_ERROR_CODE bacnet_list_write(
+ uint32_t object_instance,
+ BACNET_ARRAY_INDEX array_index,
+ bacnet_array_property_element_decode_function decode_function,
+ bacnet_list_property_element_add_function add_function,
+ BACNET_UNSIGNED_INTEGER max_elements,
+ uint8_t *apdu,
+ size_t apdu_size);
+
/* from clause 20.2.1.2 Tag Number */
/* true if extended tag numbering is used */
#define IS_EXTENDED_TAG_NUMBER(x) (((x) & 0xF0) == 0xF0)
diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h
index db2e4b44..96261d59 100644
--- a/src/bacnet/bacenum.h
+++ b/src/bacnet/bacenum.h
@@ -1683,7 +1683,13 @@ typedef enum {
/* BACnetChannelValue */
BACNET_APPLICATION_TAG_CHANNEL_VALUE,
/* BACnetLogRecord */
- BACNET_APPLICATION_TAG_LOG_RECORD
+ BACNET_APPLICATION_TAG_LOG_RECORD,
+ /* BACnetTimerStateChangeValue */
+ BACNET_APPLICATION_TAG_TIMER_VALUE,
+ /* no-value - context tagged null */
+ BACNET_APPLICATION_TAG_NO_VALUE,
+ /* ABSTRACT-SYNTAX - constructed value */
+ BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX
} BACNET_APPLICATION_TAG;
/* note: these are not the real values, */
@@ -2966,7 +2972,8 @@ typedef enum BACnetTimerTransition {
TIMER_TRANSITION_RUNNING_TO_EXPIRED = 4,
TIMER_TRANSITION_FORCED_TO_EXPIRED = 5,
TIMER_TRANSITION_EXPIRED_TO_IDLE = 6,
- TIMER_TRANSITION_EXPIRED_TO_RUNNING = 7
+ TIMER_TRANSITION_EXPIRED_TO_RUNNING = 7,
+ TIMER_TRANSITION_MAX = 8
} BACNET_TIMER_TRANSITION;
typedef enum BACnetEscalatorFault {
diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c
index d23bebdd..6c8176c7 100644
--- a/src/bacnet/bactext.c
+++ b/src/bacnet/bactext.c
@@ -177,6 +177,9 @@ INDTEXT_DATA bacnet_application_tag_names[] = {
{ BACNET_APPLICATION_TAG_SHED_LEVEL, "BACnetShedLevel" },
{ BACNET_APPLICATION_TAG_ACCESS_RULE, "BACnetAccessRule" },
{ BACNET_APPLICATION_TAG_CHANNEL_VALUE, "BACnetChannelValue" },
+ { BACNET_APPLICATION_TAG_LOG_RECORD, "BACnetLogRecord" },
+ { BACNET_APPLICATION_TAG_NO_VALUE, "BACnetNoValue" },
+ { BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX, "ABSTRACT-SYNTAX" },
{ 0, NULL }
};
@@ -225,7 +228,7 @@ INDTEXT_DATA bacnet_object_type_names[] = {
{ OBJECT_LOAD_CONTROL, "load-control" },
{ OBJECT_STRUCTURED_VIEW, "structured-view" },
{ OBJECT_ACCESS_DOOR, "access-door" },
- { OBJECT_LIGHTING_OUTPUT, "lighting-output" },
+ { OBJECT_TIMER, "timer" },
{ OBJECT_ACCESS_CREDENTIAL, "access-credential" },
{ OBJECT_ACCESS_POINT, "access-point" },
{ OBJECT_ACCESS_RIGHTS, "access-rights" },
@@ -297,7 +300,7 @@ INDTEXT_DATA bacnet_object_type_names_capitalized[] = {
{ OBJECT_LOAD_CONTROL, "Load Control" },
{ OBJECT_STRUCTURED_VIEW, "Structured View" },
{ OBJECT_ACCESS_DOOR, "Access Door" },
- { OBJECT_LIGHTING_OUTPUT, "Lighting Output" },
+ { OBJECT_TIMER, "Timer" },
{ OBJECT_ACCESS_CREDENTIAL, "Access Credential" },
{ OBJECT_ACCESS_POINT, "Access Point" },
{ OBJECT_ACCESS_RIGHTS, "Access Rights" },
@@ -2686,6 +2689,7 @@ const char *bactext_program_request_name(unsigned index)
}
INDTEXT_DATA bactext_program_state_names[] = {
+ /* BACnetProgramState enumerations */
{ PROGRAM_STATE_IDLE, "idle" },
{ PROGRAM_STATE_LOADING, "loading" },
{ PROGRAM_STATE_RUNNING, "running" },
@@ -2702,6 +2706,7 @@ const char *bactext_program_state_name(unsigned index)
}
INDTEXT_DATA bactext_program_error_names[] = {
+ /* BACnetProgramError enumerations */
{ PROGRAM_ERROR_NORMAL, "normal" },
{ PROGRAM_ERROR_LOAD_FAILED, "load-failed" },
{ PROGRAM_ERROR_INTERNAL, "internal" },
@@ -2722,6 +2727,39 @@ const char *bactext_program_error_name(unsigned index)
}
}
+INDTEXT_DATA bactext_timer_state_names[] = {
+ /* BACnetTimerState enumerations */
+ { TIMER_STATE_IDLE, "idle" },
+ { TIMER_STATE_RUNNING, "running" },
+ { TIMER_STATE_EXPIRED, "expired" },
+ { 0, NULL }
+};
+
+const char *bactext_timer_state_name(unsigned index)
+{
+ return indtext_by_index_default(
+ bactext_timer_state_names, index, ASHRAE_Reserved_String);
+}
+
+INDTEXT_DATA bactext_timer_transition_names[] = {
+ /* BACnetTimerTransition enumerations */
+ { TIMER_TRANSITION_NONE, "none" },
+ { TIMER_TRANSITION_IDLE_TO_RUNNING, "idle-to-running" },
+ { TIMER_TRANSITION_RUNNING_TO_IDLE, "running-to-idle" },
+ { TIMER_TRANSITION_RUNNING_TO_RUNNING, "running-to-running" },
+ { TIMER_TRANSITION_RUNNING_TO_EXPIRED, "running-to-expired" },
+ { TIMER_TRANSITION_FORCED_TO_EXPIRED, "forced-to-expired" },
+ { TIMER_TRANSITION_EXPIRED_TO_IDLE, "expired-to-idle" },
+ { TIMER_TRANSITION_EXPIRED_TO_RUNNING, "expired-to-running" },
+ { 0, NULL }
+};
+
+const char *bactext_timer_transition_name(unsigned index)
+{
+ return indtext_by_index_default(
+ bactext_timer_transition_names, index, ASHRAE_Reserved_String);
+}
+
/**
* @brief For a given enumerated object property string,
* find the enumeration value
@@ -2861,6 +2899,14 @@ bool bactext_object_property_strtoul(
status = bactext_strtoul_index(
bacnet_notify_type_names, search_name, found_index);
break;
+ case PROP_TIMER_STATE:
+ status = bactext_strtoul_index(
+ bactext_timer_state_names, search_name, found_index);
+ break;
+ case PROP_LAST_STATE_CHANGE:
+ status = bactext_strtoul_index(
+ bactext_timer_transition_names, search_name, found_index);
+ break;
default:
status = bactext_strtoul(search_name, found_index);
break;
diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h
index e92df155..21d5f635 100644
--- a/src/bacnet/bactext.h
+++ b/src/bacnet/bactext.h
@@ -200,6 +200,11 @@ const char *bactext_program_state_name(unsigned index);
BACNET_STACK_EXPORT
const char *bactext_program_error_name(unsigned index);
+BACNET_STACK_EXPORT
+const char *bactext_timer_transition_name(unsigned index);
+BACNET_STACK_EXPORT
+const char *bactext_timer_state_name(unsigned index);
+
BACNET_STACK_EXPORT
bool bactext_strtoul(const char *search_name, unsigned *found_index);
BACNET_STACK_EXPORT
diff --git a/src/bacnet/basic/object/bo.c b/src/bacnet/basic/object/bo.c
index e9f1510c..d6b10827 100644
--- a/src/bacnet/basic/object/bo.c
+++ b/src/bacnet/basic/object/bo.c
@@ -1032,7 +1032,7 @@ int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
uint8_t *apdu = NULL;
int apdu_size = 0;
- if ((rpdata->application_data == NULL) ||
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
diff --git a/src/bacnet/basic/object/client/device-client.c b/src/bacnet/basic/object/client/device-client.c
index 254c1fc0..1ad23e72 100644
--- a/src/bacnet/basic/object/client/device-client.c
+++ b/src/bacnet/basic/object/client/device-client.c
@@ -1216,6 +1216,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
struct special_property_list_t property_list;
#endif
+ if (!rpdata) {
+ return 0;
+ }
/* initialize the default return values */
rpdata->error_class = ERROR_CLASS_OBJECT;
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c
index e2f943fc..cfb66495 100644
--- a/src/bacnet/basic/object/device.c
+++ b/src/bacnet/basic/object/device.c
@@ -58,6 +58,7 @@
#include "bacnet/basic/object/osv.h"
#include "bacnet/basic/object/piv.h"
#include "bacnet/basic/object/time_value.h"
+#include "bacnet/basic/object/timer.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/lo.h"
#include "bacnet/basic/object/blo.h"
@@ -207,6 +208,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_TIMER, Timer_Init, Timer_Count,
+ Timer_Index_To_Instance, Timer_Valid_Instance,
+ Timer_Object_Name, Timer_Read_Property,
+ Timer_Write_Property, Timer_Property_Lists,
+ NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */,
+ NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */,
+ Timer_Add_List_Element, Timer_Remove_List_Element,
+ Timer_Create, Timer_Delete, Timer_Task },
{ OBJECT_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count,
Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance,
Integer_Value_Object_Name, Integer_Value_Read_Property,
diff --git a/src/bacnet/basic/object/nc.c b/src/bacnet/basic/object/nc.c
index 3d3b21cc..3f4bd24a 100644
--- a/src/bacnet/basic/object/nc.c
+++ b/src/bacnet/basic/object/nc.c
@@ -803,11 +803,15 @@ int Notification_Class_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
- list_element->error_class = ERROR_CLASS_SERVICES;
- list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+ /* The specified property is currently not modifiable
+ by the requester.*/
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
+ /* An array index is provided but the property is
+ not a BACnetARRAY of BACnetLIST.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
@@ -963,11 +967,15 @@ int Notification_Class_Remove_List_Element(
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
- list_element->error_class = ERROR_CLASS_SERVICES;
- list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+ /* The specified property is currently not modifiable
+ by the requester.*/
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
+ /* An array index is provided but the property is
+ not a BACnetARRAY of BACnetLIST.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
diff --git a/src/bacnet/basic/object/timer.c b/src/bacnet/basic/object/timer.c
new file mode 100644
index 00000000..14b44f11
--- /dev/null
+++ b/src/bacnet/basic/object/timer.c
@@ -0,0 +1,2278 @@
+/**
+ * @file
+ * @author Steve Karg
+ * @date October 2025
+ * @brief The Timer object type defines a standardized object whose properties
+ * represent the externally visible characteristics of a countdown timer.
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+/* BACnet Stack API */
+#include "bacnet/bacdcode.h"
+#include "bacnet/bacapp.h"
+#include "bacnet/bactext.h"
+#include "bacnet/datetime.h"
+#include "bacnet/proplist.h"
+#include "bacnet/timer_value.h"
+/* basic objects and services */
+#include "bacnet/basic/object/device.h"
+#include "bacnet/basic/services.h"
+#include "bacnet/basic/sys/debug.h"
+#include "bacnet/basic/sys/keylist.h"
+/* me! */
+#include "bacnet/basic/object/timer.h"
+
+#ifndef BACNET_TIMER_MANIPULATED_PROPERTIES_MAX
+#define BACNET_TIMER_MANIPULATED_PROPERTIES_MAX 8
+#endif
+
+/* Key List for storing the object data sorted by instance number */
+static OS_Keylist Object_List = NULL;
+/* common object type */
+static const BACNET_OBJECT_TYPE Object_Type = OBJECT_TIMER;
+static write_property_function Write_Property_Internal_Callback;
+
+struct object_data {
+ uint32_t Present_Value;
+ BACNET_TIMER_STATE Timer_State;
+ BACNET_TIMER_TRANSITION Last_State_Change;
+ BACNET_DATE_TIME Update_Time;
+ uint32_t Initial_Timeout;
+ uint32_t Default_Timeout;
+ uint32_t Min_Pres_Value;
+ uint32_t Max_Pres_Value;
+ uint32_t Resolution;
+ /* The timer state change NONE=0 has no corresponding array element.*/
+ BACNET_TIMER_STATE_CHANGE_VALUE
+ State_Change_Values[TIMER_TRANSITION_MAX - 1];
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE
+ Manipulated_Properties[BACNET_TIMER_MANIPULATED_PROPERTIES_MAX];
+ uint8_t Priority_For_Writing;
+ const char *Description;
+ const char *Object_Name;
+ BACNET_RELIABILITY Reliability;
+ bool Out_Of_Service : 1;
+ bool Changed : 1;
+ void *Context;
+};
+
+/* These three arrays are used by the ReadPropertyMultiple handler */
+static const int Properties_Required[] = {
+ /* unordered list of required properties */
+ PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME,
+ PROP_OBJECT_TYPE, PROP_PRESENT_VALUE,
+ PROP_STATUS_FLAGS, PROP_TIMER_STATE,
+ PROP_TIMER_RUNNING, -1
+};
+
+static const int Properties_Optional[] = {
+ /* unordered list of optional properties */
+ PROP_DESCRIPTION,
+ PROP_RELIABILITY,
+ PROP_OUT_OF_SERVICE,
+ PROP_UPDATE_TIME,
+ PROP_LAST_STATE_CHANGE,
+ PROP_EXPIRATION_TIME,
+ PROP_INITIAL_TIMEOUT,
+ PROP_DEFAULT_TIMEOUT,
+ PROP_MIN_PRES_VALUE,
+ PROP_MAX_PRES_VALUE,
+ PROP_RESOLUTION,
+ PROP_STATE_CHANGE_VALUES,
+ PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES,
+ PROP_PRIORITY_FOR_WRITING,
+ -1
+};
+
+static const int Properties_Proprietary[] = { -1 };
+
+/**
+ * Returns the list of required, optional, and proprietary properties.
+ * Used by ReadPropertyMultiple service.
+ *
+ * @param pRequired - pointer to list of int terminated by -1, of
+ * BACnet required properties for this object.
+ * @param pOptional - pointer to list of int terminated by -1, of
+ * BACnet optkional properties for this object.
+ * @param pProprietary - pointer to list of int terminated by -1, of
+ * BACnet proprietary properties for this object.
+ */
+void Timer_Property_Lists(
+ const int **pRequired, const int **pOptional, const int **pProprietary)
+{
+ if (pRequired) {
+ *pRequired = Properties_Required;
+ }
+ if (pOptional) {
+ *pOptional = Properties_Optional;
+ }
+ if (pProprietary) {
+ *pProprietary = Properties_Proprietary;
+ }
+
+ return;
+}
+
+/**
+ * @brief Gets an object from the list using an instance number as the key
+ * @param object_instance - object-instance number of the object
+ * @return object found in the list, or NULL if not found
+ */
+static struct object_data *Object_Data(uint32_t object_instance)
+{
+ return Keylist_Data(Object_List, object_instance);
+}
+
+/**
+ * Determines if a given Timer instance is valid
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return true if the instance is valid, and false if not
+ */
+bool Timer_Valid_Instance(uint32_t object_instance)
+{
+ struct object_data *pObject = Object_Data(object_instance);
+
+ return (pObject != NULL);
+}
+
+/**
+ * Determines the number of Timer objects
+ *
+ * @return Number of Timer objects
+ */
+unsigned Timer_Count(void)
+{
+ return Keylist_Count(Object_List);
+}
+
+/**
+ * Determines the object instance-number for a given 0..N index
+ * of Timer objects where N is Timer_Count().
+ *
+ * @param index - 0..MAX_PROGRAMS value
+ *
+ * @return object instance-number for the given index
+ */
+uint32_t Timer_Index_To_Instance(unsigned index)
+{
+ KEY key = UINT32_MAX;
+
+ Keylist_Index_Key(Object_List, index, &key);
+
+ return key;
+}
+
+/**
+ * For a given object instance-number, determines a 0..N index
+ * of Timer objects where N is Timer_Count().
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return index for the given instance-number, or MAX_PROGRAMS
+ * if not valid.
+ */
+unsigned Timer_Instance_To_Index(uint32_t object_instance)
+{
+ return Keylist_Index(Object_List, object_instance);
+}
+
+/**
+ * For a given object instance-number, determines if the member is empty
+ *
+ * Elements of the List_Of_Object_Property_References array containing
+ * object or device instance numbers equal to 4194303 are considered to
+ * be 'empty' or 'uninitialized'.
+ *
+ * @param pMember - object property reference element
+ * @return true if the member is empty
+ */
+static bool Timer_Reference_List_Member_Empty(
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember)
+{
+ bool status = false;
+
+ if (pMember) {
+ if ((pMember->objectIdentifier.instance == BACNET_MAX_INSTANCE) ||
+ (pMember->deviceIdentifier.instance == BACNET_MAX_INSTANCE)) {
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, returns the list member element
+ * @param object_instance - object-instance number of the object
+ * @param list_index - 1-based list index of members
+ * @return pointer to list member element or NULL if not found
+ */
+BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Timer_Reference_List_Member_Element(
+ uint32_t object_instance, unsigned list_index)
+{
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
+ struct object_data *pObject;
+ unsigned i, count = 0;
+
+ pObject = Object_Data(object_instance);
+ if (pObject && (list_index > 0)) {
+ for (i = 0; i < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; i++) {
+ pMember = &pObject->Manipulated_Properties[i];
+ if (!Timer_Reference_List_Member_Empty(pMember)) {
+ count++;
+ }
+ if (count == list_index) {
+ return pMember;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Encode a BACnetList property element
+ * @param object_instance [in] BACnet object instance number
+ * @param list_index [in] list index requested:
+ * 0 to N for individual list members
+ * @param apdu [out] Buffer in which the APDU contents are built, or NULL to
+ * return the length of buffer if it had been built
+ * @return The length of the apdu encoded or 0 if invalid member
+ */
+static int Timer_List_Of_Object_Property_References_Encode(
+ uint32_t object_instance, uint32_t list_index, uint8_t *apdu)
+{
+ int apdu_len = 0;
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *value;
+
+ value =
+ Timer_Reference_List_Member_Element(object_instance, list_index + 1);
+ if (value) {
+ apdu_len = bacapp_encode_device_obj_property_ref(apdu, value);
+ }
+
+ return apdu_len;
+}
+
+/**
+ * For a given object instance-number, set the member element values
+ * @param pObject - object in which to set the value
+ * @param index - 0-based array index
+ * @param pMember - pointer to member value, or NULL to set as 'empty'
+ * @return true if set, false if not set
+ */
+static bool List_Of_Object_Property_References_Set(
+ struct object_data *pObject,
+ unsigned index,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember)
+{
+ bool status = false;
+ if (pObject && (index < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX)) {
+ if (pMember) {
+ memcpy(
+ &pObject->Manipulated_Properties[index], pMember,
+ sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE));
+ } else {
+ pObject->Manipulated_Properties[index].objectIdentifier.instance =
+ BACNET_MAX_INSTANCE;
+ pObject->Manipulated_Properties[index].deviceIdentifier.instance =
+ BACNET_MAX_INSTANCE;
+ pObject->Manipulated_Properties[index].objectIdentifier.type =
+ OBJECT_LIGHTING_OUTPUT;
+ pObject->Manipulated_Properties[index].objectIdentifier.instance =
+ BACNET_MAX_INSTANCE;
+ pObject->Manipulated_Properties[index].propertyIdentifier =
+ PROP_PRESENT_VALUE;
+ pObject->Manipulated_Properties[index].arrayIndex =
+ BACNET_ARRAY_ALL;
+ pObject->Manipulated_Properties[index].deviceIdentifier.type =
+ OBJECT_DEVICE;
+ pObject->Manipulated_Properties[index].deviceIdentifier.instance =
+ BACNET_MAX_INSTANCE;
+ }
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, set the member element value
+ * @param object_instance - object-instance number of the object
+ * @param index - zero-based array index reference list array
+ * @param pMember - member values to set, or NULL to set as 'empty'
+ * @return pointer to member element or NULL if not found
+ */
+bool Timer_Reference_List_Member_Element_Set(
+ uint32_t object_instance,
+ unsigned index,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ status =
+ List_Of_Object_Property_References_Set(pObject, index, pMember);
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, determines the member count
+ * @param object_instance - object-instance number of the object
+ * @return member count
+ */
+unsigned Timer_Reference_List_Member_Capacity(uint32_t object_instance)
+{
+ (void)object_instance;
+ return BACNET_TIMER_MANIPULATED_PROPERTIES_MAX;
+}
+
+/**
+ * @brief For a given object instance-number, adds a unique member element
+ * to the list
+ * @param object_instance - object-instance number of the object
+ * @param pMemberSrc - pointer to a object property reference element
+ * @return true if the element was added, false if the element was not added
+ */
+bool Timer_Reference_List_Member_Element_Add(
+ uint32_t object_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pNewMember)
+{
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
+ unsigned m = 0;
+ struct object_data *pObject;
+
+ if (Timer_Reference_List_Member_Empty(pNewMember)) {
+ /* The element value is out of range for the property. */
+ return false;
+ }
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ /* is the element already in the list? */
+ for (m = 0; m < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; m++) {
+ pMember = &pObject->Manipulated_Properties[m];
+ if (!Timer_Reference_List_Member_Empty(pMember)) {
+ if (bacnet_device_object_property_reference_same(
+ pNewMember, pMember)) {
+ return true;
+ }
+ }
+ }
+ for (m = 0; m < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; m++) {
+ pMember = &pObject->Manipulated_Properties[m];
+ if (Timer_Reference_List_Member_Empty(pMember)) {
+ /* first empty slot */
+ memcpy(
+ pMember, pNewMember,
+ sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE));
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief For a given object instance-number, removes a list element
+ * @param object_instance - object-instance number of the object
+ * @param pMemberSrc - pointer to a object property reference element,
+ * or NULL to remove ALL
+ * @return true if removed, false if not removed
+ */
+bool Timer_Reference_List_Member_Element_Remove(
+ uint32_t object_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pRemoveMember)
+{
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
+ unsigned m = 0;
+ bool status = false;
+ struct object_data *pObject;
+
+ if (Timer_Reference_List_Member_Empty(pRemoveMember)) {
+ /* The element value is out of range for the property. */
+ return false;
+ }
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ for (m = 0; m < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; m++) {
+ pMember = &pObject->Manipulated_Properties[m];
+ if (!Timer_Reference_List_Member_Empty(pMember)) {
+ if (pRemoveMember) {
+ if (bacnet_device_object_property_reference_same(
+ pRemoveMember, pMember)) {
+ List_Of_Object_Property_References_Set(
+ pObject, m, NULL);
+ status = true;
+ }
+ } else {
+ List_Of_Object_Property_References_Set(pObject, m, NULL);
+ status = true;
+ }
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, determines the BACnetLIST count
+ * @param object_instance - object-instance number of the object
+ * @return BACnetLIST count
+ */
+unsigned Timer_Reference_List_Member_Element_Count(uint32_t object_instance)
+{
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
+ unsigned count = 0, i;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ for (i = 0; i < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; i++) {
+ pMember = &pObject->Manipulated_Properties[i];
+ if (!Timer_Reference_List_Member_Empty(pMember)) {
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+/**
+ * For a given object instance-number, sets the present-value at a given
+ * priority 1..16.
+ *
+ * @param pObject - object instance data
+ * @param value - application value
+ * @param priority - BACnet priority 0=none,1..16
+ *
+ * @return true if values are within range and present-value is sent.
+ */
+static bool Timer_Write_Members(
+ struct object_data *pObject,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value,
+ uint8_t priority)
+{
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ bool status = false;
+ unsigned i = 0;
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
+
+ if (pObject && value) {
+ for (i = 0; i < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; i++) {
+ pMember = &pObject->Manipulated_Properties[i];
+ if (Timer_Reference_List_Member_Empty(pMember)) {
+ continue;
+ }
+ if ((pMember->deviceIdentifier.type == OBJECT_DEVICE) &&
+ (pMember->deviceIdentifier.instance != BACNET_MAX_INSTANCE) &&
+ (pMember->objectIdentifier.instance != BACNET_MAX_INSTANCE)) {
+ wp_data.object_type = pMember->objectIdentifier.type;
+ wp_data.object_instance = pMember->objectIdentifier.instance;
+ wp_data.object_property = pMember->propertyIdentifier;
+ wp_data.array_index = pMember->arrayIndex;
+ wp_data.error_class = ERROR_CLASS_PROPERTY;
+ wp_data.error_code = ERROR_CODE_SUCCESS;
+ wp_data.priority = priority;
+ wp_data.application_data_len = bacnet_timer_value_encode(
+ wp_data.application_data, sizeof(wp_data.application_data),
+ value);
+ if (Write_Property_Internal_Callback) {
+ status = Write_Property_Internal_Callback(&wp_data);
+ if (status) {
+ wp_data.error_code = ERROR_CODE_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief initiate the write requests for the current transition
+ * @param object_instance - object-instance number of the object
+ * @return true if the write occurred
+ */
+static bool Timer_Write_Request_Initiate(struct object_data *pObject)
+{
+ bool status = false;
+ unsigned index = 0;
+ BACNET_TIMER_STATE_CHANGE_VALUE *value = NULL;
+
+ if (pObject) {
+ if (pObject->Last_State_Change != TIMER_TRANSITION_NONE) {
+ index = pObject->Last_State_Change;
+ if (index < TIMER_TRANSITION_MAX) {
+ index--;
+ value = &pObject->State_Change_Values[index];
+ }
+ }
+ if (value) {
+ status = Timer_Write_Members(
+ pObject, value, pObject->Priority_For_Writing);
+ }
+ }
+
+ return status;
+}
+
+/**
+ * For a given object instance-number, determines the program-state
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return program-state of the object
+ */
+BACNET_TIMER_STATE Timer_State(uint32_t object_instance)
+{
+ BACNET_TIMER_STATE value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Timer_State;
+ }
+
+ return value;
+}
+
+/**
+ * @brief For a given object instance-number, sets the timer-state
+ * @details This property, of type BACnetTimerState, indicates the
+ * current state of the timer this object represents.
+ * To clear the timer, i.e. to request the timer to enter the IDLE state,
+ * a value of IDLE is written to this property.
+ *
+ * Writing this value to this property while in the RUNNING or EXPIRED
+ * state will force the timer to enter the IDLE state.
+ * If already in the IDLE state, no state transition occurs
+ * if this value is written.
+ *
+ * @param object_instance - object-instance number of the object
+ * @param value - integer value
+ *
+ * @return return false if a value other than IDLE is written to this property
+ */
+bool Timer_State_Set(uint32_t object_instance, BACNET_TIMER_STATE value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (value == TIMER_STATE_IDLE) {
+ /* Clear Request */
+ /* If a value of IDLE is written to the Timer_State property */
+ if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ /* set Timer_State to IDLE;
+ set Last_State_Change to RUNNING_TO_IDLE;
+ set Present_Value to zero;
+ set Update_Time to the current date and time;
+ initiate the write requests for the RUNNING_TO_IDLE
+ transition if present;
+ and enter the IDLE state. */
+ pObject->Timer_State = TIMER_STATE_IDLE;
+ pObject->Present_Value = 0;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ pObject->Last_State_Change = TIMER_TRANSITION_RUNNING_TO_IDLE;
+ Timer_Write_Request_Initiate(pObject);
+ } else if (pObject->Timer_State == TIMER_STATE_EXPIRED) {
+ pObject->Last_State_Change = TIMER_TRANSITION_EXPIRED_TO_IDLE;
+ /* then set Timer_State to IDLE;
+ set Last_State_Change to EXPIRED_TO_IDLE;
+ set Update_Time to current date and time;
+ initiate the write requests for the EXPIRED_TO_IDLE
+ transition if present; and enter the IDLE state. */
+ pObject->Timer_State = TIMER_STATE_IDLE;
+ pObject->Present_Value = 0;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ pObject->Last_State_Change = TIMER_TRANSITION_EXPIRED_TO_IDLE;
+ Timer_Write_Request_Initiate(pObject);
+ } else if (pObject->Timer_State == TIMER_STATE_IDLE) {
+ /* then no properties shall be changed;
+ no write requests shall be initiated;
+ and no state transition shall occur.*/
+ }
+ /* Writing a value other than IDLE to this property
+ shall cause a Result(-) to be returned */
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, gets the timer
+ * running status.
+ * @details
+ * 12.57.11 Timer_Running
+ * This property, of type Boolean, shall have a value of TRUE if
+ * the current state of the timer is RUNNING, otherwise FALSE.
+ * This property may be used by other objects that require a
+ * simple Boolean flag for determining if the timer is in RUNNING state.
+ * @param object_instance - object-instance number of the object
+ * @return TRUE if the current state of the timer is RUNNING, otherwise FALSE.
+ */
+bool Timer_Running(uint32_t object_instance)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, sets the timer
+ * running status
+ * @details Writing a value of TRUE to this property, in any timer state,
+ * shall be considered a start request. Present_Value shall be set to the
+ * value specified in the Default_Timeout property.
+ * Writing a value of FALSE to this property while the timer is in the
+ * RUNNING state shall be considered an expire request and shall force
+ * the timer to transition to state EXPIRED. When writing a value of FALSE
+ * to this property while the timer is in the EXPIRED or IDLE state,
+ * no transition of the timer state shall occur.
+ * @param object_instance - object-instance number of the object
+ * @param start - true if start is request, false if stop is requested
+ * @return true if the running status was set
+ */
+bool Timer_Running_Set(uint32_t object_instance, bool start)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (start) {
+ if (pObject->Timer_State == TIMER_STATE_IDLE) {
+ /* If a value of TRUE is written to the Timer_Running property,
+ then set Initial_Timeout to the value of Default_Timeout;
+ set Timer_State to RUNNING;
+ set Last_State_Change to IDLE_TO_RUNNING;
+ set Present_Value to the value of Initial_Timeout;
+ set Update_Time to the current date and time;
+ initiate the write requests for the IDLE_TO_RUNNING
+ transition if present; and enter the RUNNING state. */
+ pObject->Timer_State = TIMER_STATE_RUNNING;
+ pObject->Last_State_Change = TIMER_TRANSITION_IDLE_TO_RUNNING;
+ pObject->Initial_Timeout = pObject->Default_Timeout;
+ pObject->Present_Value = pObject->Initial_Timeout;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ } else if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ /* If a value of TRUE is written to the Timer_Running property,
+ then set Last_State_Change to RUNNING_TO_RUNNING;
+ set Initial_Timeout to the value of Default_Timeout;
+ set Present_Value to the value of Initial_Timeout;
+ set Update_Time to the current date and time;
+ initiate the write requests for the RUNNING_TO_RUNNING
+ transition if present; and enter the RUNNING state.*/
+ pObject->Timer_State = TIMER_STATE_RUNNING;
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_RUNNING_TO_RUNNING;
+ pObject->Initial_Timeout = pObject->Default_Timeout;
+ pObject->Present_Value = pObject->Initial_Timeout;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ } else if (pObject->Timer_State == TIMER_STATE_EXPIRED) {
+ /* If a value of TRUE is written to the Timer_Running property,
+ set Timer_State to RUNNING;
+ set Last_State_Change to EXPIRED_TO_RUNNING;
+ set Initial_Timeout to the value of Default_Timeout;
+ set Present_Value to the value of Initial_Timeout;
+ set Update_Time to the current date and time;
+ initiate the write requests for the EXPIRED_TO_RUNNING
+ transition if present; and enter the RUNNING state. */
+ pObject->Timer_State = TIMER_STATE_RUNNING;
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_EXPIRED_TO_RUNNING;
+ pObject->Initial_Timeout = pObject->Default_Timeout;
+ pObject->Present_Value = pObject->Initial_Timeout;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ }
+ } else {
+ if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ /* Expire Request
+ If a value of FALSE is written to the Timer_Running
+ property,
+ set Timer_State to EXPIRED;
+ set Last_State_Change to FORCED_TO_EXPIRED;
+ set Present_Value to zero;
+ set Update_Time to the current date and time;
+ initiate the write requests for the FORCED_TO_EXPIRED
+ transition if present; and enter the EXPIRED state.*/
+ pObject->Timer_State = TIMER_STATE_EXPIRED;
+ pObject->Last_State_Change = TIMER_TRANSITION_FORCED_TO_EXPIRED;
+ pObject->Present_Value = 0;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ }
+ }
+ 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 Timer_Object_Name(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
+{
+ char text[32] = "";
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Object_Name) {
+ status =
+ characterstring_init_ansi(object_name, pObject->Object_Name);
+ } else {
+ snprintf(
+ text, sizeof(text), "TIMER-%lu",
+ (unsigned long)object_instance);
+ status = characterstring_init_ansi(object_name, text);
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, sets the object-name
+ * @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 Timer_Name_Set(uint32_t object_instance, const char *new_name)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ status = true;
+ pObject->Object_Name = new_name;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Return the object name C string
+ * @param object_instance [in] BACnet object instance number
+ * @return object name or NULL if not found
+ */
+const char *Timer_Name_ASCII(uint32_t object_instance)
+{
+ const char *name = NULL;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ name = pObject->Object_Name;
+ }
+
+ return name;
+}
+
+/**
+ * For a given object instance-number, return the description.
+ * @param object_instance - object-instance number of the object
+ * @param description - description pointer
+ * @return true/false
+ */
+bool Timer_Description(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *description)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Description) {
+ status =
+ characterstring_init_ansi(description, pObject->Description);
+ } else {
+ status = characterstring_init_ansi(description, "");
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief 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 string was set
+ */
+bool Timer_Description_Set(uint32_t object_instance, const char *new_name)
+{
+ bool status = false; /* return value */
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ status = true;
+ pObject->Description = new_name;
+ }
+
+ return status;
+}
+
+/**
+ * @brief 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
+ */
+const char *Timer_Description_ANSI(uint32_t object_instance)
+{
+ const char *name = NULL;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ if (pObject->Description == NULL) {
+ name = "";
+ } else {
+ name = pObject->Description;
+ }
+ }
+
+ return name;
+}
+
+/**
+ * For a given object instance-number, returns the program change value
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return program change property value
+ */
+BACNET_TIMER_TRANSITION Timer_Last_State_Change(uint32_t object_instance)
+{
+ BACNET_TIMER_TRANSITION change = TIMER_TRANSITION_NONE;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ change = pObject->Last_State_Change;
+ }
+
+ return change;
+}
+
+/**
+ * For a given object instance-number, returns the out-of-service
+ * property value
+ *
+ * @param object_instance - object-instance number of the object
+ *
+ * @return out-of-service property value
+ */
+bool Timer_Out_Of_Service(uint32_t object_instance)
+{
+ struct object_data *pObject;
+ bool value = false;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Out_Of_Service;
+ }
+
+ return value;
+}
+
+/**
+ * For a given object instance-number, sets the out-of-service property value
+ *
+ * @param object_instance - object-instance number of the object
+ * @param value - boolean out-of-service value
+ *
+ * @return true if the out-of-service property value was set
+ */
+void Timer_Out_Of_Service_Set(uint32_t object_instance, bool value)
+{
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Out_Of_Service = value;
+ }
+}
+
+/**
+ * @brief For a given object instance-number, gets the reliability.
+ * @param object_instance - object-instance number of the object
+ * @return reliability value
+ */
+BACNET_RELIABILITY Timer_Reliability(uint32_t object_instance)
+{
+ BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ reliability = pObject->Reliability;
+ }
+
+ return reliability;
+}
+
+/**
+ * @brief For a given object instance-number, gets the Fault status flag
+ * @param object_instance - object-instance number of the object
+ * @return true the status flag is in Fault
+ */
+static bool Timer_Fault(uint32_t object_instance)
+{
+ struct object_data *pObject;
+ bool fault = false;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (pObject->Reliability != RELIABILITY_NO_FAULT_DETECTED) {
+ fault = true;
+ }
+ }
+
+ return fault;
+}
+
+/**
+ * @brief For a given object instance-number, sets the reliability
+ * @param object_instance - object-instance number of the object
+ * @param value - reliability enumerated value
+ * @return true if values are within range and property is set.
+ */
+bool Timer_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value)
+{
+ struct object_data *pObject;
+ bool status = false;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (value <= 255) {
+ pObject->Reliability = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Return the present-value for a specific object instance
+ * @param object_instance [in] BACnet network port object instance number
+ * @return the present-value for a specific object instance
+ */
+uint32_t Timer_Present_Value(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value = pObject->Present_Value;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Set the present-value for a specific object instance
+ * @details Writing a value to this property that is within the supported
+ * range, defined by Min_Pres_Value and Max_Pres_Value, shall force
+ * the timer to transition to the RUNNING state.
+ * The value written shall be used as the initial timeout
+ * and set into the Initial_Timeout property.
+ *
+ * Writing a value of zero to this property while the timer
+ * is in the RUNNING state shall be considered an expire request
+ * and force the timer state to transition to state EXPIRED.
+ * If a value of zero is written to the property while the timer
+ * is in the EXPIRED or IDLE state, then no transition of the
+ * timer state shall occur.
+ *
+ * @param object_instance [in] BACnet network port object instance number
+ * @return true if the present-value for a specific object instance was set
+ */
+bool Timer_Present_Value_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (value == 0) {
+ /* If a value of zero is written to the Present_Value property */
+ if ((pObject->Timer_State == TIMER_STATE_IDLE) ||
+ (pObject->Timer_State == TIMER_STATE_EXPIRED)) {
+ /* then no properties shall be changed;
+ no write requests shall be initiated;
+ and no state transition shall occur. */
+ } else if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ /* set Timer_State to EXPIRED;
+ set Last_State_Change to FORCED_TO_EXPIRED;
+ set Present_Value to zero;
+ set Update_Time to the current date and time;
+ initiate the write requests for
+ the FORCED_TO_EXPIRED transition if present;
+ and enter the EXPIRED state.*/
+ pObject->Timer_State = TIMER_STATE_EXPIRED;
+ pObject->Last_State_Change = TIMER_TRANSITION_FORCED_TO_EXPIRED;
+ pObject->Present_Value = 0;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ }
+ status = true;
+ } else {
+ if ((value >= pObject->Min_Pres_Value) &&
+ (value <= pObject->Max_Pres_Value)) {
+ /* Start Request with Specific Timeout */
+ /* If a value within the supported range is written
+ to the Present_Value property.
+ set Initial_Timeout to the value written to Present_Value;*/
+ pObject->Present_Value = value;
+ pObject->Initial_Timeout = value;
+ if (pObject->Timer_State == TIMER_STATE_IDLE) {
+ /* set Last_State_Change to IDLE_TO_RUNNING; */
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_IDLE_TO_RUNNING;
+ } else if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ /* set Last_State_Change to RUNNING_TO_RUNNING; */
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_RUNNING_TO_RUNNING;
+ } else if (pObject->Timer_State == TIMER_STATE_EXPIRED) {
+ /* set Last_State_Change to EXPIRED_TO_RUNNING; */
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_EXPIRED_TO_RUNNING;
+ }
+ /* set Timer_State to RUNNING;
+ set Update_Time to the current date and time;
+ initiate the write requests for the transition if present;
+ and enter the RUNNING state */
+ pObject->Timer_State = TIMER_STATE_RUNNING;
+ datetime_local(
+ &pObject->Update_Time.date, &pObject->Update_Time.time,
+ NULL, NULL);
+ Timer_Write_Request_Initiate(pObject);
+ status = true;
+ } else {
+ status = false;
+ }
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Get the update-time property value for the object-instance specified
+ * @param object_instance [in] BACnet network port object instance number
+ * @return true if property value was retrieved
+ */
+bool Timer_Update_Time(uint32_t object_instance, BACNET_DATE_TIME *bdatetime)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ datetime_copy(bdatetime, &pObject->Update_Time);
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Get the update-time property value for the object-instance specified
+ * @param object_instance [in] BACnet network port object instance number
+ * @return true if property value was retrieved
+ */
+bool Timer_Update_Time_Set(
+ uint32_t object_instance, BACNET_DATE_TIME *bdatetime)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ datetime_copy(&pObject->Update_Time, bdatetime);
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Get the expiration-time property value for the object-instance
+ * specified
+ * @details The Expiration_Time property shall indicate the date and time
+ * when the timer will expire. The value of Expiration_Time
+ * shall be calculated at the time the property is read.
+ * @param object_instance [in] BACnet network port object instance number
+ * @param bdatetime [out] the property value retrieved
+ * @return true if property value was retrieved
+ */
+bool Timer_Expiration_Time(
+ uint32_t object_instance, BACNET_DATE_TIME *bdatetime)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (pObject->Timer_State == TIMER_STATE_RUNNING) {
+ datetime_copy(bdatetime, &pObject->Update_Time);
+ datetime_add_milliseconds(bdatetime, pObject->Present_Value);
+ } else {
+ /* set Expiration_Time to the unspecified datetime value */
+ datetime_wildcard_set(bdatetime);
+ }
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the initial-timeout property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return initial-timeout property value
+ */
+uint32_t Timer_Initial_Timeout(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Initial_Timeout;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the initial-timeout property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the initial-timeout property value was set
+ */
+bool Timer_Initial_Timeout_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Initial_Timeout = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the default-timeout property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return default-timeout property value
+ */
+uint32_t Timer_Default_Timeout(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Default_Timeout;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the default-timeout property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the default-timeout property value was set
+ */
+bool Timer_Default_Timeout_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Default_Timeout = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the min-pres-value property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return min-pres-value property value
+ */
+uint32_t Timer_Min_Pres_Value(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Min_Pres_Value;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the min-pres-value property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the min-pres-value property value was set
+ */
+bool Timer_Min_Pres_Value_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Min_Pres_Value = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the max-pres-value property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return max-pres-value property value
+ */
+uint32_t Timer_Max_Pres_Value(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Max_Pres_Value;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the max-pres-value property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the max-pres-value property value was set
+ */
+bool Timer_Max_Pres_Value_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Max_Pres_Value = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the resolution property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @return resolution property value
+ */
+uint32_t Timer_Resolution(uint32_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Resolution;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the resolution property value for a given object instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the resolution property value was set
+ */
+bool Timer_Resolution_Set(uint32_t object_instance, uint32_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Resolution = value;
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Gets the priority-for-writing property value for a given object
+ * instance
+ * @param object_instance - object-instance number of the object
+ * @return priority-for-writing property value
+ */
+uint8_t Timer_Priority_For_Writing(uint8_t object_instance)
+{
+ uint32_t value = 0;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ value = pObject->Priority_For_Writing;
+ }
+
+ return value;
+}
+
+/**
+ * @brief Sets the priority-for-writing property value for a given object
+ * instance
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to set
+ * @return true if the priority-for-writing property value was in range and set
+ */
+bool Timer_Priority_For_Writing_Set(uint32_t object_instance, uint8_t value)
+{
+ bool status = false;
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ /* Unsigned(1..16) */
+ if ((value >= BACNET_MIN_PRIORITY) && (value <= BACNET_MAX_PRIORITY)) {
+ pObject->Priority_For_Writing = value;
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Encode a BACnetARRAY property element
+ * @details This property, of type BACnetARRAY[7]
+ * of BACnetTimerStateChangeValue, represents the values that
+ * are to be written to the referenced properties when a change
+ * of the timer state occurs.
+ * Each of the elements 1-7 of this array may contain a value
+ * to be written for the respective change of timer state.
+ * The array index of the element is equal to the numerical value of the
+ * BACnetTimerTransition enumeration for the respective timer state change.
+ * The timer state change NONE has no corresponding array element.
+ * @param object_instance [in] BACnet network port object instance number
+ * @param index [in] array index requested:
+ * 0 to N for individual array members
+ * @param apdu [out] Buffer in which the APDU contents are built, or NULL to
+ * return the length of buffer if it had been built
+ * @return The length of the apdu encoded or
+ * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX
+ */
+static int Timer_State_Change_Value_Encode(
+ uint32_t object_instance, BACNET_ARRAY_INDEX index, uint8_t *apdu)
+{
+ int apdu_len = BACNET_STATUS_ERROR;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ /* Note: The timer state change NONE=0
+ has no corresponding array element.*/
+ if (index < (TIMER_TRANSITION_MAX - 1)) {
+ apdu_len = bacnet_timer_value_type_encode(
+ apdu, &pObject->State_Change_Values[index]);
+ }
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Get the state-change value array element value
+ * @param object_instance - BACnet network port object instance number
+ * @param transition - the state-change enumeration requested
+ * @return state-change structure or NULL if transition is out of range.
+ */
+BACNET_TIMER_STATE_CHANGE_VALUE *Timer_State_Change_Value(
+ uint32_t object_instance, BACNET_TIMER_TRANSITION transition)
+{
+ BACNET_TIMER_STATE_CHANGE_VALUE *value = NULL;
+ unsigned index;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ /* Note: The timer state change NONE=0
+ has no corresponding array element.*/
+ if ((transition != TIMER_TRANSITION_NONE) &&
+ (transition < TIMER_TRANSITION_MAX)) {
+ index = transition - 1;
+ value = &pObject->State_Change_Values[index];
+ }
+ }
+
+ return value;
+}
+
+/**
+ * @brief Set the state-change value array element value
+ * @param object_instance - BACnet network port object instance number
+ * @param transition - the state-change enumeration requested
+ * @param value state-change structure values
+ * @return true if the transition is in range and values were copied
+ */
+bool Timer_State_Change_Value_Set(
+ uint32_t object_instance,
+ BACNET_TIMER_TRANSITION transition,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value)
+{
+ bool status = false;
+ unsigned index;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ /* Note: The timer state change NONE=0
+ has no corresponding array element.*/
+ if ((transition != TIMER_TRANSITION_NONE) &&
+ (transition < TIMER_TRANSITION_MAX)) {
+ index = transition - 1;
+ status = bacnet_timer_value_copy(
+ &pObject->State_Change_Values[index], value);
+ }
+ }
+
+ return status;
+}
+
+/**
+ * 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, zero if no data, or
+ * BACNET_STATUS_ERROR on error.
+ */
+int Timer_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
+{
+ int apdu_len = 0; /* return value */
+ BACNET_BIT_STRING bit_string;
+ BACNET_CHARACTER_STRING char_string;
+ BACNET_DATE_TIME bdatetime;
+ BACNET_UNSIGNED_INTEGER unsigned_value;
+ size_t i;
+ unsigned imax;
+ uint8_t *apdu = NULL;
+ int apdu_size;
+
+ uint32_t enum_value = 0;
+ bool state = false;
+
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
+ (rpdata->application_data_len == 0)) {
+ return 0;
+ }
+ apdu = rpdata->application_data;
+ apdu_size = rpdata->application_data_len;
+ switch (rpdata->object_property) {
+ case PROP_OBJECT_IDENTIFIER:
+ apdu_len = encode_application_object_id(
+ &apdu[0], Object_Type, rpdata->object_instance);
+ break;
+ case PROP_OBJECT_NAME:
+ Timer_Object_Name(rpdata->object_instance, &char_string);
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ break;
+ case PROP_OBJECT_TYPE:
+ apdu_len = encode_application_enumerated(&apdu[0], Object_Type);
+ break;
+ case PROP_STATUS_FLAGS:
+ bitstring_init(&bit_string);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
+ state = Timer_Fault(rpdata->object_instance);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, state);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
+ state = Timer_Out_Of_Service(rpdata->object_instance);
+ bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state);
+ apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
+ break;
+ case PROP_TIMER_STATE:
+ enum_value = Timer_State(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_PRESENT_VALUE:
+ unsigned_value = Timer_Present_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_TIMER_RUNNING:
+ state = Timer_Running(rpdata->object_instance);
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_DESCRIPTION:
+ if (Timer_Description(rpdata->object_instance, &char_string)) {
+ apdu_len =
+ encode_application_character_string(&apdu[0], &char_string);
+ }
+ break;
+ case PROP_RELIABILITY:
+ enum_value = Timer_Reliability(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_OUT_OF_SERVICE:
+ state = Timer_Out_Of_Service(rpdata->object_instance);
+ apdu_len = encode_application_boolean(&apdu[0], state);
+ break;
+ case PROP_UPDATE_TIME:
+ Timer_Update_Time(rpdata->object_instance, &bdatetime);
+ apdu_len = bacapp_encode_datetime(&apdu[0], &bdatetime);
+ break;
+ case PROP_LAST_STATE_CHANGE:
+ enum_value = Timer_Last_State_Change(rpdata->object_instance);
+ apdu_len = encode_application_enumerated(&apdu[0], enum_value);
+ break;
+ case PROP_EXPIRATION_TIME:
+ Timer_Expiration_Time(rpdata->object_instance, &bdatetime);
+ apdu_len = bacapp_encode_datetime(&apdu[0], &bdatetime);
+ break;
+ case PROP_INITIAL_TIMEOUT:
+ unsigned_value = Timer_Initial_Timeout(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_DEFAULT_TIMEOUT:
+ unsigned_value = Timer_Default_Timeout(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_MIN_PRES_VALUE:
+ unsigned_value = Timer_Min_Pres_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_MAX_PRES_VALUE:
+ unsigned_value = Timer_Max_Pres_Value(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_RESOLUTION:
+ unsigned_value = Timer_Resolution(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ case PROP_STATE_CHANGE_VALUES:
+ /* note: The timer state change NONE=0 has no
+ corresponding array element.*/
+ apdu_len = bacnet_array_encode(
+ rpdata->object_instance, rpdata->array_index,
+ Timer_State_Change_Value_Encode, TIMER_TRANSITION_MAX - 1, apdu,
+ apdu_size);
+ if (apdu_len == BACNET_STATUS_ABORT) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ } else if (apdu_len == BACNET_STATUS_ERROR) {
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
+ }
+ break;
+ case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES:
+ imax = Timer_Reference_List_Member_Element_Count(
+ rpdata->object_instance);
+ for (i = 0; i < imax; i++) {
+ apdu_len += Timer_List_Of_Object_Property_References_Encode(
+ rpdata->object_instance, i, &apdu[apdu_len]);
+ }
+ break;
+ case PROP_PRIORITY_FOR_WRITING:
+ unsigned_value =
+ Timer_Priority_For_Writing(rpdata->object_instance);
+ apdu_len = encode_application_unsigned(&apdu[0], unsigned_value);
+ break;
+ default:
+ rpdata->error_class = ERROR_CLASS_PROPERTY;
+ rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ apdu_len = BACNET_STATUS_ERROR;
+ break;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Decode a BACnetARRAY property element to determine the element length
+ * @param object_instance [in] BACnet network port object instance number
+ * @param apdu [in] Buffer in which the APDU contents are extracted
+ * @param apdu_size [in] The size of the APDU buffer
+ * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error
+ */
+static int Timer_State_Change_Value_Length(
+ uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
+{
+ BACNET_TIMER_STATE_CHANGE_VALUE value = { 0 };
+ int len = 0;
+
+ (void)object_instance;
+ len = bacnet_timer_value_decode(apdu, apdu_size, &value);
+
+ return len;
+}
+
+/**
+ * @brief Write a value to a BACnetARRAY property element value
+ * using a BACnetARRAY write utility function
+ * @param object_instance [in] BACnet network port object instance number
+ * @param array_index [in] array index to write:
+ * 0=array size, 1 to N for individual array members
+ * @param application_data [in] encoded element value
+ * @param application_data_len [in] The size of the encoded element value
+ * @return BACNET_ERROR_CODE value
+ */
+static BACNET_ERROR_CODE Timer_State_Change_Value_Write(
+ uint32_t object_instance,
+ BACNET_ARRAY_INDEX array_index,
+ uint8_t *application_data,
+ size_t application_data_len)
+{
+ BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ BACNET_TIMER_STATE_CHANGE_VALUE new_value = { 0 }, *current_value = NULL;
+ int len = 0;
+ bool status;
+
+ if (array_index == 0) {
+ /* fixed size array */
+ error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else if (array_index < TIMER_TRANSITION_MAX) {
+ len = bacnet_timer_value_decode(
+ application_data, application_data_len, &new_value);
+ if (len > 0) {
+ current_value =
+ Timer_State_Change_Value(object_instance, array_index);
+ status = bacnet_timer_value_copy(current_value, &new_value);
+ if (status) {
+ error_code = ERROR_CODE_SUCCESS;
+ } else {
+ error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ } else {
+ error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ }
+ } else {
+ error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
+ }
+
+ return error_code;
+}
+
+/**
+ * @brief Decode a BACnetLIST property element to determine the element length
+ * @param object_instance [in] BACnet network port object instance number
+ * @param apdu [in] Buffer in which the APDU contents are extracted
+ * @param apdu_size [in] The size of the APDU buffer
+ * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error
+ */
+static int Timer_List_Of_Object_Property_References_Length(
+ uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
+{
+ BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ int len = 0;
+
+ (void)object_instance;
+ len = bacapp_decode_known_property(
+ apdu, apdu_size, &value, OBJECT_TIMER,
+ PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES);
+
+ return len;
+}
+
+/**
+ * @brief Add an element to a BACnetLIST property
+ * using a BACnetLIST add utility function
+ * @param object_instance [in] BACnet network port object instance number
+ * @param application_data [in] encoded element value
+ * @param application_data_len [in] The size of the encoded element value
+ * @return BACNET_ERROR_CODE value or ERROR_CODE_SUCCESS
+ */
+static BACNET_ERROR_CODE Timer_List_Of_Object_Property_References_Add(
+ uint32_t object_instance,
+ uint8_t *application_data,
+ size_t application_data_len)
+{
+ BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE new_value = { 0 };
+ int len = 0;
+ bool status = false;
+
+ if ((!application_data) && (application_data_len == 0)) {
+ /* empty the BACnetLIST - remove all before adding */
+ (void)Timer_Reference_List_Member_Element_Remove(object_instance, NULL);
+ error_code = ERROR_CODE_SUCCESS;
+ } else {
+ len = bacnet_device_object_property_reference_decode(
+ application_data, application_data_len, &new_value);
+ if (len > 0) {
+ if (Timer_Reference_List_Member_Empty(&new_value)) {
+ /* The element value is out of range for the property. */
+ error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ } else {
+ status = Timer_Reference_List_Member_Element_Add(
+ object_instance, &new_value);
+ if (status) {
+ error_code = ERROR_CODE_SUCCESS;
+ } else {
+ error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
+ }
+ }
+ } else {
+ error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ }
+ }
+
+ return error_code;
+}
+
+/**
+ * @brief Remove an element from a BACnetLIST property
+ * @param object_instance [in] BACnet network port object instance number
+ * @param application_data [in] encoded element value
+ * @param application_data_len [in] The size of the encoded element value
+ * @return BACNET_ERROR_CODE value or ERROR_CODE_SUCCESS
+ */
+static BACNET_ERROR_CODE Timer_List_Of_Object_Property_References_Remove(
+ uint32_t object_instance,
+ uint8_t *application_data,
+ size_t application_data_len)
+{
+ BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE new_value = { 0 };
+ int len = 0;
+ bool status = false;
+
+ if ((!application_data) && (application_data_len == 0)) {
+ /* nothing to remove */
+ error_code = ERROR_CODE_SUCCESS;
+ } else {
+ len = bacnet_device_object_property_reference_decode(
+ application_data, application_data_len, &new_value);
+ if (len > 0) {
+ if (Timer_Reference_List_Member_Empty(&new_value)) {
+ error_code = ERROR_CODE_LIST_ELEMENT_NOT_FOUND;
+ } else {
+ status = Timer_Reference_List_Member_Element_Remove(
+ object_instance, &new_value);
+ if (status) {
+ error_code = ERROR_CODE_SUCCESS;
+ } else {
+ error_code = ERROR_CODE_LIST_ELEMENT_NOT_FOUND;
+ }
+ }
+ } else {
+ error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ }
+ }
+
+ return error_code;
+}
+
+/**
+ * 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 Timer_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
+{
+ bool status = false; /* return value */
+ int len = 0;
+ BACNET_APPLICATION_DATA_VALUE value = { 0 };
+
+ /* decode the some of the request */
+ len = bacapp_decode_known_array_property(
+ wp_data->application_data, wp_data->application_data_len, &value,
+ wp_data->object_type, wp_data->object_property, wp_data->array_index);
+ 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;
+ }
+ switch (wp_data->object_property) {
+ case PROP_PRESENT_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ status = Timer_Present_Value_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_OUT_OF_SERVICE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
+ if (status) {
+ Timer_Out_Of_Service_Set(
+ wp_data->object_instance, value.type.Boolean);
+ }
+ break;
+ case PROP_DEFAULT_TIMEOUT:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT32_MAX) {
+ status = Timer_Default_Timeout_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_MIN_PRES_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT32_MAX) {
+ status = Timer_Min_Pres_Value_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_MAX_PRES_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT32_MAX) {
+ status = Timer_Max_Pres_Value_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_RESOLUTION:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT32_MAX) {
+ status = Timer_Resolution_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_PRIORITY_FOR_WRITING:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
+ if (status) {
+ if (value.type.Unsigned_Int <= UINT8_MAX) {
+ status = Timer_Priority_For_Writing_Set(
+ wp_data->object_instance, value.type.Unsigned_Int);
+ } else {
+ status = false;
+ }
+ if (!status) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+ }
+ break;
+ case PROP_STATE_CHANGE_VALUES:
+ wp_data->error_code = bacnet_array_write(
+ wp_data->object_instance, wp_data->array_index,
+ Timer_State_Change_Value_Length, Timer_State_Change_Value_Write,
+ TIMER_TRANSITION_MAX - 1, wp_data->application_data,
+ wp_data->application_data_len);
+ if (wp_data->error_code == ERROR_CODE_SUCCESS) {
+ status = true;
+ }
+ break;
+ case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES:
+ /* a BACnetLIST can only be written all-at-once */
+ wp_data->error_code = bacnet_list_write(
+ wp_data->object_instance, wp_data->array_index,
+ Timer_List_Of_Object_Property_References_Length,
+ Timer_List_Of_Object_Property_References_Add,
+ BACNET_TIMER_MANIPULATED_PROPERTIES_MAX,
+ wp_data->application_data, wp_data->application_data_len);
+ if (wp_data->error_code == ERROR_CODE_SUCCESS) {
+ status = true;
+ }
+ break;
+ default:
+ if (property_lists_member(
+ Properties_Required, Properties_Optional,
+ Properties_Proprietary, wp_data->object_property)) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ } else {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
+ }
+ break;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Set the context used with load, unload, run, halt, and restart
+ * @param object_instance [in] BACnet object instance number
+ * @param context [in] pointer to the context
+ */
+void *Timer_Context_Get(uint32_t object_instance)
+{
+ struct object_data *pObject = Object_Data(object_instance);
+
+ if (pObject) {
+ return pObject->Context;
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Set the context used for vendor specific extensions
+ * @param object_instance [in] BACnet object instance number
+ * @param context [in] pointer to the context
+ */
+void Timer_Context_Set(uint32_t object_instance, void *context)
+{
+ struct object_data *pObject;
+
+ pObject = Object_Data(object_instance);
+ if (pObject) {
+ pObject->Context = context;
+ }
+}
+
+/**
+ * @brief Sets a callback used when the timer is written from BACnet
+ * @param cb - callback used to provide indications
+ */
+void Timer_Write_Property_Internal_Callback_Set(write_property_function cb)
+{
+ Write_Property_Internal_Callback = cb;
+}
+
+/**
+ * @brief Updates the object program operation
+ * @details In the RUNNING state, the timer is active
+ * and is counting down the remaining time.
+ * The Present_Value property shall indicate the
+ * remaining time until expiration.
+ *
+ * @param object_instance - object-instance number of the object
+ * @param milliseconds - number of milliseconds elapsed
+ */
+void Timer_Task(uint32_t object_instance, uint16_t milliseconds)
+{
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ switch (pObject->Timer_State) {
+ case TIMER_STATE_RUNNING:
+ if (pObject->Present_Value > milliseconds) {
+ pObject->Present_Value -= milliseconds;
+ } else {
+ /* expired */
+ pObject->Present_Value = 0;
+ pObject->Timer_State = TIMER_STATE_EXPIRED;
+ pObject->Last_State_Change =
+ TIMER_TRANSITION_RUNNING_TO_EXPIRED;
+ }
+ break;
+ case TIMER_STATE_EXPIRED:
+ case TIMER_STATE_IDLE:
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+/**
+ * @brief AddListElement to a list property
+ * @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
+ * which is packed with the information from the request.
+ * @return #BACNET_STATUS_OK or #BACNET_STATUS_ERROR or
+ * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT
+ */
+int Timer_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
+{
+ if (!list_element) {
+ return BACNET_STATUS_ABORT;
+ }
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ if (list_element->object_property ==
+ PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) {
+ if (list_element->array_index != BACNET_ARRAY_ALL) {
+ /* An array index is provided but the property is
+ not a BACnetARRAY of BACnetLIST.*/
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ } else {
+ list_element->error_code =
+ Timer_List_Of_Object_Property_References_Add(
+ list_element->object_instance,
+ list_element->application_data,
+ list_element->application_data_len);
+ if (list_element->error_code == ERROR_CODE_SUCCESS) {
+ return BACNET_STATUS_OK;
+ }
+ if (list_element->error_code ==
+ ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY) {
+ list_element->error_class = ERROR_CLASS_RESOURCES;
+ list_element->error_code =
+ ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT;
+ }
+ }
+ } else {
+ list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ }
+
+ return BACNET_STATUS_ERROR;
+}
+
+/**
+ * @brief RemoveListElement to a list property
+ * @param list_element [in] Pointer to the BACnet_List_Element_Data structure,
+ * which is packed with the information from the request.
+ * @return #BACNET_STATUS_OK or #BACNET_STATUS_ERROR or
+ * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT
+ */
+int Timer_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
+{
+ if (!list_element) {
+ return BACNET_STATUS_ABORT;
+ }
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ if (list_element->object_property ==
+ PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) {
+ if (list_element->array_index != BACNET_ARRAY_ALL) {
+ /* An array index is provided but the property is
+ not a BACnetARRAY of BACnetLIST.*/
+ list_element->error_class = ERROR_CLASS_PROPERTY;
+ list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
+ } else {
+ list_element->error_code =
+ Timer_List_Of_Object_Property_References_Remove(
+ list_element->object_instance,
+ list_element->application_data,
+ list_element->application_data_len);
+ if (list_element->error_code == ERROR_CODE_SUCCESS) {
+ return BACNET_STATUS_OK;
+ }
+ if (list_element->error_code == ERROR_CODE_LIST_ELEMENT_NOT_FOUND) {
+ list_element->error_class = ERROR_CLASS_SERVICES;
+ }
+ }
+ } else {
+ list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
+ }
+
+ return BACNET_STATUS_ERROR;
+}
+
+/**
+ * @brief Creates a Timer object
+ * @param object_instance - object-instance number of the object
+ * @return the object-instance that was created, or BACNET_MAX_INSTANCE
+ */
+uint32_t Timer_Create(uint32_t object_instance)
+{
+ struct object_data *pObject = NULL;
+ int index;
+ unsigned i;
+
+ if (!Object_List) {
+ Object_List = Keylist_Create();
+ }
+ 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) {
+ /* already exists - signal success but don't change data */
+ return object_instance;
+ }
+ pObject = calloc(1, sizeof(struct object_data));
+ if (!pObject) {
+ /* no RAM available - signal failure */
+ return BACNET_MAX_INSTANCE;
+ }
+ index = Keylist_Data_Add(Object_List, object_instance, pObject);
+ if (index < 0) {
+ /* unable to add to list - signal failure */
+ free(pObject);
+ return BACNET_MAX_INSTANCE;
+ }
+ pObject->Timer_State = TIMER_STATE_IDLE;
+ pObject->Last_State_Change = TIMER_TRANSITION_NONE;
+ datetime_wildcard_set(&pObject->Update_Time);
+ pObject->Initial_Timeout = 0;
+ pObject->Default_Timeout = 1000;
+ pObject->Min_Pres_Value = 1;
+ pObject->Max_Pres_Value = UINT32_MAX;
+ pObject->Resolution = 1;
+ pObject->Priority_For_Writing = BACNET_MAX_PRIORITY;
+ pObject->Description = NULL;
+ pObject->Object_Name = NULL;
+ pObject->Reliability = RELIABILITY_NO_FAULT_DETECTED;
+ pObject->Out_Of_Service = false;
+ pObject->Changed = false;
+ pObject->Context = NULL;
+ for (i = 0; i < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; i++) {
+ List_Of_Object_Property_References_Set(pObject, i, NULL);
+ }
+
+ return object_instance;
+}
+
+/**
+ * @brief Deletes an object-instance
+ * @param object_instance - object-instance number of the object
+ * @return true if the object-instance was deleted
+ */
+bool Timer_Delete(uint32_t object_instance)
+{
+ bool status = false;
+ struct object_data *pObject =
+ Keylist_Data_Delete(Object_List, object_instance);
+
+ if (pObject) {
+ free(pObject);
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Deletes all the objects and their data
+ */
+void Timer_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 object data
+ */
+void Timer_Init(void)
+{
+ if (!Object_List) {
+ Object_List = Keylist_Create();
+ }
+}
diff --git a/src/bacnet/basic/object/timer.h b/src/bacnet/basic/object/timer.h
new file mode 100644
index 00000000..1fe212b7
--- /dev/null
+++ b/src/bacnet/basic/object/timer.h
@@ -0,0 +1,189 @@
+/**
+ * @file
+ * @brief API for Timer object type
+ * @author Steve Karg
+ * @date October 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#ifndef BACNET_BASIC_OBJECT_TIMER_H
+#define BACNET_BASIC_OBJECT_TIMER_H
+#include
+#include
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+/* BACnet Stack API */
+#include "bacnet/bacerror.h"
+#include "bacnet/timer_value.h"
+#include "bacnet/wp.h"
+#include "bacnet/rp.h"
+#include "bacnet/list_element.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+BACNET_STACK_EXPORT
+void Timer_Property_Lists(
+ const int **pRequired, const int **pOptional, const int **pProprietary);
+BACNET_STACK_EXPORT
+bool Timer_Valid_Instance(uint32_t object_instance);
+BACNET_STACK_EXPORT
+unsigned Timer_Count(void);
+BACNET_STACK_EXPORT
+uint32_t Timer_Index_To_Instance(unsigned index);
+BACNET_STACK_EXPORT
+unsigned Timer_Instance_To_Index(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Timer_Object_Name(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *object_name);
+BACNET_STACK_EXPORT
+bool Timer_Name_Set(uint32_t object_instance, const char *new_name);
+BACNET_STACK_EXPORT
+const char *Timer_Name_ASCII(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+int Timer_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata);
+
+BACNET_STACK_EXPORT
+bool Timer_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data);
+
+BACNET_STACK_EXPORT
+bool Timer_Description(
+ uint32_t object_instance, BACNET_CHARACTER_STRING *description);
+BACNET_STACK_EXPORT
+bool Timer_Description_Set(uint32_t instance, const char *new_name);
+BACNET_STACK_EXPORT
+const char *Timer_Description_ANSI(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Timer_Out_Of_Service(uint32_t instance);
+BACNET_STACK_EXPORT
+void Timer_Out_Of_Service_Set(uint32_t instance, bool oos_flag);
+
+BACNET_STACK_EXPORT
+BACNET_RELIABILITY Timer_Reliability(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Present_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Present_Value_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+BACNET_TIMER_STATE Timer_State(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_State_Set(uint32_t object_instance, BACNET_TIMER_STATE value);
+
+BACNET_STACK_EXPORT
+BACNET_TIMER_TRANSITION Timer_Last_State_Change(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+bool Timer_Running(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Running_Set(uint32_t object_instance, bool running);
+
+BACNET_STACK_EXPORT
+bool Timer_Update_Time(uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
+BACNET_STACK_EXPORT
+bool Timer_Update_Time_Set(
+ uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
+
+BACNET_STACK_EXPORT
+bool Timer_Expiration_Time(
+ uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Initial_Timeout(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Initial_Timeout_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Default_Timeout(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Default_Timeout_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Min_Pres_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Min_Pres_Value_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Max_Pres_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Max_Pres_Value_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Resolution(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Resolution_Set(uint32_t object_instance, uint32_t value);
+
+BACNET_STACK_EXPORT
+uint8_t Timer_Priority_For_Writing(uint8_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Priority_For_Writing_Set(uint32_t object_instance, uint8_t value);
+
+BACNET_STACK_EXPORT
+unsigned Timer_Reference_List_Member_Capacity(uint32_t object_instance);
+BACNET_STACK_EXPORT
+BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Timer_Reference_List_Member_Element(
+ uint32_t object_instance, unsigned list_index);
+BACNET_STACK_EXPORT
+bool Timer_Reference_List_Member_Element_Set(
+ uint32_t object_instance,
+ unsigned index,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember);
+BACNET_STACK_EXPORT
+bool Timer_Reference_List_Member_Element_Add(
+ uint32_t object_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pNewMember);
+BACNET_STACK_EXPORT
+bool Timer_Reference_List_Member_Element_Remove(
+ uint32_t object_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pRemoveMember);
+BACNET_STACK_EXPORT
+unsigned Timer_Reference_List_Member_Element_Count(uint32_t object_instance);
+
+BACNET_STACK_EXPORT
+BACNET_TIMER_STATE_CHANGE_VALUE *Timer_State_Change_Value(
+ uint32_t object_instance, BACNET_TIMER_TRANSITION transition);
+BACNET_STACK_EXPORT
+bool Timer_State_Change_Value_Set(
+ uint32_t object_instance,
+ BACNET_TIMER_TRANSITION transition,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value);
+
+BACNET_STACK_EXPORT
+void Timer_Task(uint32_t object_instance, uint16_t milliseconds);
+
+BACNET_STACK_EXPORT
+void Timer_Write_Property_Internal_Callback_Set(write_property_function cb);
+
+BACNET_STACK_EXPORT
+int Timer_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element);
+
+BACNET_STACK_EXPORT
+int Timer_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element);
+
+BACNET_STACK_EXPORT
+uint32_t Timer_Create(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Timer_Delete(uint32_t object_instance);
+BACNET_STACK_EXPORT
+void Timer_Cleanup(void);
+BACNET_STACK_EXPORT
+void Timer_Init(void);
+
+/* API for the program requests
+ note: return value is 0 for success, non-zero for failure
+*/
+BACNET_STACK_EXPORT
+void *Timer_Context_Get(uint32_t object_instance);
+BACNET_STACK_EXPORT
+void Timer_Context_Set(uint32_t object_instance, void *context);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c
index 4bf7d854..1c3f8049 100644
--- a/src/bacnet/basic/server/bacnet_device.c
+++ b/src/bacnet/basic/server/bacnet_device.c
@@ -53,6 +53,7 @@
#include "bacnet/basic/object/csv.h"
#include "bacnet/basic/object/iv.h"
#include "bacnet/basic/object/time_value.h"
+#include "bacnet/basic/object/timer.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/object/lo.h"
@@ -129,6 +130,7 @@
defined(CONFIG_BACNET_BASIC_OBJECT_FILE) || \
defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW) || \
defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) || \
+ defined(CONFIG_BACNET_BASIC_OBJECT_TIMER) || \
defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) || \
defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE))
#define CONFIG_BACNET_BASIC_OBJECT_ALL
@@ -158,6 +160,7 @@
#define CONFIG_BACNET_BASIC_OBJECT_FILE
#define CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW
#define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE
+#define CONFIG_BACNET_BASIC_OBJECT_TIMER
#define CONFIG_BACNET_BASIC_OBJECT_PROGRAM
#define CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE
#endif
@@ -185,6 +188,10 @@
#undef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT
#warning "Network Port is configured, but BACnet Protocol Revision < 17"
#endif
+#ifdef CONFIG_BACNET_BASIC_OBJECT_TIMER
+#undef CONFIG_BACNET_BASIC_OBJECT_TIMER
+#warning "Timer is configured, but BACnet Protocol Revision < 17"
+#endif
#endif
#if (BACNET_PROTOCOL_REVISION < 24)
@@ -750,6 +757,28 @@ static object_functions_t My_Object_Table[] = {
CharacterString_Value_Delete,
NULL /* Timer */ },
#endif
+#if defined(CONFIG_BACNET_BASIC_OBJECT_TIMER)
+ { OBJECT_TIMER,
+ Timer_Init,
+ Timer_Count,
+ Timer_Index_To_Instance,
+ Timer_Valid_Instance,
+ Timer_Object_Name,
+ Timer_Read_Property,
+ Timer_Write_Property,
+ Timer_Property_Lists,
+ NULL /* ReadRangeInfo */,
+ NULL /* Iterator */,
+ NULL /* Value_Lists */,
+ NULL /* COV */,
+ NULL /* COV Clear */,
+ NULL /* Intrinsic Reporting */,
+ Timer_Add_List_Element,
+ Timer_Remove_List_Element,
+ Timer_Create,
+ Timer_Delete,
+ Timer_Task },
+#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM)
{ OBJECT_BITSTRING_VALUE,
Program_Init,
@@ -1783,8 +1812,16 @@ uint32_t Device_Interval_Offset(void)
}
#endif
-/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
- BACNET_STATUS_ABORT for abort message */
+/**
+ * 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, zero if no data, or
+ * BACNET_STATUS_ERROR on error.
+ */
int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
{
int apdu_len = 0; /* return value */
@@ -2007,7 +2044,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
* @param pObject - object table
* @param rpdata [in,out] Structure with the requested Object & Property info
* on entry, and APDU message on return.
- * @return The length of the APDU on success, else BACNET_STATUS_ERROR
+ * @return number of APDU bytes in the response, zero if no data, or
+ * BACNET_STATUS_ERROR on error.
*/
static int Read_Property_Common(
const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata)
@@ -2019,7 +2057,7 @@ static int Read_Property_Common(
struct special_property_list_t property_list;
#endif
- if ((rpdata->application_data == NULL) ||
+ if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
@@ -2061,6 +2099,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
int apdu_len = BACNET_STATUS_ERROR;
struct object_functions *pObject = NULL;
+ if (!rpdata) {
+ return 0;
+ }
/* initialize the default return values */
rpdata->error_class = ERROR_CLASS_OBJECT;
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
diff --git a/src/bacnet/basic/service/h_list_element.c b/src/bacnet/basic/service/h_list_element.c
index cdffa196..a4d40b88 100644
--- a/src/bacnet/basic/service/h_list_element.c
+++ b/src/bacnet/basic/service/h_list_element.c
@@ -52,7 +52,7 @@ void handler_add_list_element(
BACNET_LIST_ELEMENT_DATA list_element = { 0 };
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
- int len = 0;
+ int len = 0, err = 0;
bool status = true;
int pdu_len = 0;
int bytes_sent = 0;
@@ -102,7 +102,15 @@ void handler_add_list_element(
status = false;
}
if (status) {
- if (Device_Add_List_Element(&list_element) >= 0) {
+ if (!property_list_bacnet_list_member(
+ list_element.object_type, list_element.object_property)) {
+ list_element.error_class = ERROR_CLASS_SERVICES;
+ list_element.error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+ err = BACNET_STATUS_ERROR;
+ } else {
+ err = Device_Add_List_Element(&list_element);
+ }
+ if (err == BACNET_STATUS_OK) {
len = encode_simple_ack(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_ADD_LIST_ELEMENT);
@@ -153,7 +161,7 @@ void handler_remove_list_element(
BACNET_LIST_ELEMENT_DATA list_element = { 0 };
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
- int len = 0;
+ int len = 0, err = BACNET_STATUS_OK;
bool status = true;
int pdu_len = 0;
int bytes_sent = 0;
@@ -192,28 +200,35 @@ void handler_remove_list_element(
(long)list_element.array_index);
} else {
debug_print("RemoveListElement: Unable to decode request!\n");
+ /* bad decoding or something we didn't understand - send an abort */
+ err = BACNET_STATUS_ABORT;
}
- /* bad decoding or something we didn't understand - send an abort */
- if (len <= 0) {
+ if (len > 0) {
+ if (!property_list_bacnet_list_member(
+ list_element.object_type, list_element.object_property)) {
+ list_element.error_class = ERROR_CLASS_SERVICES;
+ list_element.error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
+ err = BACNET_STATUS_ERROR;
+ } else {
+ err = Device_Remove_List_Element(&list_element);
+ }
+ }
+ if (err == BACNET_STATUS_OK) {
+ len = encode_simple_ack(
+ &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
+ SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT);
+ debug_print("RemoveListElement: Sending Simple Ack!\n");
+ } else if (err == BACNET_STATUS_ABORT) {
len = abort_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
ABORT_REASON_OTHER, true);
debug_print("RemoveListElement: Bad Encoding. Sending Abort!\n");
- status = false;
- }
- if (status) {
- if (Device_Remove_List_Element(&list_element) >= 0) {
- len = encode_simple_ack(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT);
- debug_print("RemoveListElement: Sending Simple Ack!\n");
- } else {
- len = bacerror_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT,
- list_element.error_class, list_element.error_code);
- debug_print("RemoveListElement: Sending Error!\n");
- }
+ } else {
+ len = bacerror_encode_apdu(
+ &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
+ SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, list_element.error_class,
+ list_element.error_code);
+ debug_print("RemoveListElement: Sending Error!\n");
}
}
/* Send PDU */
diff --git a/src/bacnet/config.h b/src/bacnet/config.h
index 8626267a..b429cc85 100644
--- a/src/bacnet/config.h
+++ b/src/bacnet/config.h
@@ -244,6 +244,8 @@
defined(BACAPP_SHED_LEVEL) || \
defined(BACAPP_ACCESS_RULE) || \
defined(BACAPP_CHANNEL_VALUE) || \
+ defined(BACAPP_TIMER_VALUE) || \
+ defined(BACAPP_NO_VALUE) || \
defined(BACAPP_LOG_RECORD) || \
defined(BACAPP_SECURE_CONNECT) || \
defined(BACAPP_TYPES_EXTRA))
@@ -294,6 +296,8 @@
#define BACAPP_SHED_LEVEL
#define BACAPP_ACCESS_RULE
#define BACAPP_CHANNEL_VALUE
+#define BACAPP_TIMER_VALUE
+#define BACAPP_NO_VALUE
#define BACAPP_LOG_RECORD
#define BACAPP_SECURE_CONNECT
#endif
@@ -321,6 +325,8 @@
defined(BACAPP_SHED_LEVEL) || \
defined(BACAPP_ACCESS_RULE) || \
defined(BACAPP_CHANNEL_VALUE) || \
+ defined(BACAPP_TIMER_VALUE) || \
+ defined(BACAPP_NO_VALUE) || \
defined(BACAPP_LOG_RECORD)
#define BACAPP_COMPLEX_TYPES
#endif
diff --git a/src/bacnet/datetime.c b/src/bacnet/datetime.c
index c7a41489..d06065c8 100644
--- a/src/bacnet/datetime.c
+++ b/src/bacnet/datetime.c
@@ -613,13 +613,12 @@ void datetime_add_minutes(BACNET_DATE_TIME *bdatetime, int32_t minutes)
uint32_t bdatetime_days = 0;
int32_t days = 0;
- /* convert bdatetime to seconds and days */
+ /* convert bdatetime to minutes and days */
bdatetime_minutes =
datetime_hms_to_seconds_since_midnight(
bdatetime->time.hour, bdatetime->time.min, bdatetime->time.sec) /
60;
bdatetime_days = datetime_days_since_epoch(&bdatetime->date);
-
/* more minutes than in a day? */
days = minutes / (24 * 60);
bdatetime_days += days;
@@ -645,14 +644,86 @@ void datetime_add_minutes(BACNET_DATE_TIME *bdatetime, int32_t minutes)
bdatetime_days += days;
bdatetime_minutes -= (days * 24 * 60);
}
-
- /* convert bdatetime from seconds and days */
+ /* convert bdatetime from minutes and days */
datetime_hms_from_seconds_since_midnight(
bdatetime_minutes * 60, &bdatetime->time.hour, &bdatetime->time.min,
NULL);
datetime_days_since_epoch_into_date(bdatetime_days, &bdatetime->date);
}
+/**
+ * @brief Add or subtract seconds to a BACnet DateTime structure
+ * @param bdatetime [in] the starting date and time
+ * @param seconds [in] number of seconds to add or subtract from the time
+ */
+void datetime_add_seconds(BACNET_DATE_TIME *bdatetime, int32_t seconds)
+{
+ uint32_t bdatetime_seconds = 0;
+ uint32_t bdatetime_days = 0;
+ int32_t days = 0;
+
+ /* convert bdatetime to seconds and days */
+ bdatetime_seconds = datetime_hms_to_seconds_since_midnight(
+ bdatetime->time.hour, bdatetime->time.min, bdatetime->time.sec);
+ bdatetime_days = datetime_days_since_epoch(&bdatetime->date);
+ /* more minutes than in a day? */
+ days = seconds / (24 * 60 * 60);
+ bdatetime_days += days;
+ seconds -= (days * 24 * 60 * 60);
+ /* less seconds - previous day? */
+ if (seconds < 0) {
+ /* convert to positive for easier math */
+ seconds *= -1;
+ if ((uint32_t)seconds > bdatetime_seconds) {
+ /* previous day */
+ bdatetime_days -= 1;
+ bdatetime_seconds += ((24 * 60 * 60) - seconds);
+ } else {
+ bdatetime_seconds -= seconds;
+ days = bdatetime_seconds / (24 * 60 * 60);
+ bdatetime_days += days;
+ bdatetime_seconds -= (days * 24 * 60 * 60);
+ }
+ } else {
+ /* more days than current datetime? */
+ bdatetime_seconds += seconds;
+ days = bdatetime_seconds / (24 * 60 * 60);
+ bdatetime_days += days;
+ bdatetime_seconds -= (days * 24 * 60 * 60);
+ }
+ /* convert bdatetime from seconds and days */
+ datetime_hms_from_seconds_since_midnight(
+ bdatetime_seconds, &bdatetime->time.hour, &bdatetime->time.min,
+ &bdatetime->time.sec);
+ datetime_days_since_epoch_into_date(bdatetime_days, &bdatetime->date);
+}
+
+/**
+ * @brief Add milliseconds to a BACnet DateTime structure
+ * @param bdatetime [in] the starting date and time
+ * @param milliseconds [in] number of milliseconds to add to the date and time
+ */
+void datetime_add_milliseconds(
+ BACNET_DATE_TIME *bdatetime, int32_t milliseconds)
+{
+ uint32_t bdatetime_hundredths = 0;
+ int32_t hundredths = 0;
+ int32_t seconds = 0;
+
+ /* convert to seconds */
+ seconds = milliseconds / 1000;
+ hundredths = (milliseconds - (seconds * 1000)) / 10;
+ bdatetime_hundredths = bdatetime->time.hundredths;
+ bdatetime_hundredths += hundredths;
+ if (bdatetime_hundredths >= 100) {
+ /* adjust the seconds if hundredths overflow */
+ seconds++;
+ bdatetime_hundredths -= 100;
+ }
+ datetime_add_seconds(bdatetime, seconds);
+ bdatetime->time.hundredths = bdatetime_hundredths;
+}
+
/**
* @brief Calculates the number of seconds since epoch from datetime
* @param bdatetime [in] the starting date and time
diff --git a/src/bacnet/datetime.h b/src/bacnet/datetime.h
index 969ef835..d2047862 100644
--- a/src/bacnet/datetime.h
+++ b/src/bacnet/datetime.h
@@ -185,9 +185,14 @@ void datetime_copy_time(BACNET_TIME *dest, const BACNET_TIME *src);
BACNET_STACK_EXPORT
void datetime_copy(BACNET_DATE_TIME *dest, const BACNET_DATE_TIME *src);
-/* utility add or subtract minutes function */
+/* utility add or subtract time function */
BACNET_STACK_EXPORT
void datetime_add_minutes(BACNET_DATE_TIME *bdatetime, int32_t minutes);
+BACNET_STACK_EXPORT
+void datetime_add_seconds(BACNET_DATE_TIME *bdatetime, int32_t seconds);
+BACNET_STACK_EXPORT
+void datetime_add_milliseconds(
+ BACNET_DATE_TIME *bdatetime, int32_t milliseconds);
BACNET_STACK_EXPORT
bacnet_time_t datetime_seconds_since_epoch(const BACNET_DATE_TIME *bdatetime);
diff --git a/src/bacnet/proplist.c b/src/bacnet/proplist.c
index 5ee22903..d119cab2 100644
--- a/src/bacnet/proplist.c
+++ b/src/bacnet/proplist.c
@@ -408,7 +408,8 @@ const int *property_list_bacnet_array(void)
bool property_list_bacnet_array_member(
BACNET_OBJECT_TYPE object_type, BACNET_PROPERTY_ID object_property)
{
- /* exceptions where property is an BACnetARRAY only in specific objects */
+ /* exceptions where a property is an BACnetARRAY or a BACnetLIST
+ only in specific object types */
switch (object_type) {
case OBJECT_GLOBAL_GROUP:
switch (object_property) {
@@ -459,6 +460,7 @@ static const int Properties_BACnetLIST[] = {
PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS,
PROP_ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS,
PROP_LIST_OF_GROUP_MEMBERS,
+ PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES,
PROP_ACCEPTED_MODES,
PROP_LIFE_SAFETY_ALARM_VALUES,
PROP_ALARM_VALUES,
@@ -524,11 +526,10 @@ bool property_list_bacnet_list_member(
break;
}
break;
- case OBJECT_SCHEDULE:
- case OBJECT_TIMER:
+ case OBJECT_CHANNEL:
switch (object_property) {
case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES:
- return true;
+ return false;
default:
break;
}
diff --git a/src/bacnet/timer_value.c b/src/bacnet/timer_value.c
new file mode 100644
index 00000000..afad399d
--- /dev/null
+++ b/src/bacnet/timer_value.c
@@ -0,0 +1,904 @@
+/**
+ * @file
+ * @brief BACnetTimerStateChangeValue data type encoding and decoding
+ * @author Steve Karg
+ * @date October 2025
+ * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+/* BACnet Stack API */
+#include "bacnet/bacdcode.h"
+#include "bacnet/bacstr.h"
+#include "bacnet/bacint.h"
+#include "bacnet/bacreal.h"
+#include "bacnet/timer_value.h"
+
+/**
+ * @brief Encode a context tagged BACnetTimerStateChangeValue with no-value type
+ * @param apdu buffer to be encoded, or NULL for length
+ * @param tag_number - context tag number to be encoded
+ * @param value The value to be encoded.
+ * @return the number of apdu bytes encoded
+ */
+int bacnet_timer_value_no_value_encode(uint8_t *apdu)
+{
+ int len = 0;
+ int apdu_len = 0;
+ const uint8_t tag_number = 0;
+
+ /* no-value[0] Null */
+ len = encode_opening_tag(apdu, tag_number);
+ apdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
+ len = encode_application_null(apdu);
+ apdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
+ len = encode_closing_tag(apdu, tag_number);
+ apdu_len += len;
+
+ return apdu_len;
+}
+
+/**
+ * @brief Decodes a context tagged BACnetTimerStateChangeValue with
+ * no-value type
+ * @param apdu - the APDU buffer
+ * @param apdu_size - the APDU buffer size
+ * @return length of the APDU buffer decoded, or BACNET_STATUS_ERROR
+ */
+int bacnet_timer_value_no_value_decode(const uint8_t *apdu, uint32_t apdu_size)
+{
+ int apdu_len = 0;
+ int len;
+ const uint8_t tag_number = 0;
+
+ /* no-value[0] Null */
+ if (!bacnet_is_opening_tag_number(
+ &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
+ return BACNET_STATUS_ERROR;
+ }
+ apdu_len += len;
+ len = bacnet_null_application_decode(&apdu[apdu_len], apdu_size - apdu_len);
+ if (len < 0) {
+ return BACNET_STATUS_ERROR;
+ }
+ apdu_len += len;
+ if (!bacnet_is_closing_tag_number(
+ &apdu[apdu_len], apdu_size - apdu_len, tag_number, &len)) {
+ return BACNET_STATUS_ERROR;
+ }
+ apdu_len += len;
+
+ return apdu_len;
+}
+
+/**
+ * @brief Print a no-value to a string for EPICS
+ * @param str - destination string, or NULL for length only
+ * @param str_len - length of the destination string, or 0 for length only
+ * @return number of characters written
+ */
+int bacnet_timer_value_no_value_to_ascii(char *str, size_t str_len)
+{
+ return snprintf(str, str_len, "no-value");
+}
+
+/**
+ * @brief Parse a string into a application tag value
+ * @param tag [out] The application tag value if found
+ * @param argv [in] The string to parse
+ * @return true on success, else false
+ */
+bool bacnet_timer_value_no_value_from_ascii(uint8_t *tag, const char *argv)
+{
+ bool status = false;
+
+ if (bacnet_stricmp(argv, "no-value") == 0) {
+ if (tag) {
+ *tag = BACNET_APPLICATION_TAG_NO_VALUE;
+ }
+ status = true;
+ }
+
+ return status;
+}
+
+/**
+ * @brief Encode a given BACnetTimerStateChangeValue
+ * @param apdu - APDU buffer for storing the encoded data, or NULL for length
+ * @param value - BACNET_TIMER_STATE_CHANGE_VALUE value
+ * @return number of bytes in the APDU
+ */
+int bacnet_timer_value_type_encode(
+ uint8_t *apdu, const BACNET_TIMER_STATE_CHANGE_VALUE *value)
+{
+ int apdu_len = 0;
+
+ if (!value) {
+ return 0;
+ }
+ switch (value->tag) {
+ case BACNET_APPLICATION_TAG_NULL:
+ apdu_len = encode_application_null(apdu);
+ break;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ case BACNET_APPLICATION_TAG_BOOLEAN:
+ apdu_len = encode_application_boolean(apdu, value->type.Boolean);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ case BACNET_APPLICATION_TAG_UNSIGNED_INT:
+ apdu_len =
+ encode_application_unsigned(apdu, value->type.Unsigned_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ case BACNET_APPLICATION_TAG_SIGNED_INT:
+ apdu_len = encode_application_signed(apdu, value->type.Signed_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ case BACNET_APPLICATION_TAG_REAL:
+ apdu_len = encode_application_real(apdu, value->type.Real);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ case BACNET_APPLICATION_TAG_DOUBLE:
+ apdu_len = encode_application_double(apdu, value->type.Double);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_OCTET_STRING)
+ case BACNET_APPLICATION_TAG_OCTET_STRING:
+ apdu_len = encode_application_octet_string(
+ apdu, &value->type.Octet_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_CHARACTER_STRING)
+ case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ apdu_len = encode_application_character_string(
+ apdu, &value->type.Character_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_BIT_STRING)
+ case BACNET_APPLICATION_TAG_BIT_STRING:
+ apdu_len =
+ encode_application_bitstring(apdu, &value->type.Bit_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_ENUMERATED)
+ case BACNET_APPLICATION_TAG_ENUMERATED:
+ apdu_len =
+ encode_application_enumerated(apdu, value->type.Enumerated);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATE)
+ case BACNET_APPLICATION_TAG_DATE:
+ apdu_len = encode_application_date(apdu, &value->type.Date);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_TIME)
+ case BACNET_APPLICATION_TAG_TIME:
+ apdu_len = encode_application_time(apdu, &value->type.Time);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_OBJECT_ID)
+ case BACNET_APPLICATION_TAG_OBJECT_ID:
+ apdu_len = encode_application_object_id(
+ apdu, (int)value->type.Object_Id.type,
+ value->type.Object_Id.instance);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_NO_VALUE)
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ /* no-value[0] Null */
+ apdu_len = bacnet_timer_value_no_value_encode(apdu);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE)
+ case BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX:
+ /* constructed-value[1] ABSTRACT-SYNTAX.&Type */
+ apdu_len = bacnet_constructed_value_context_encode(
+ apdu, 1, &value->type.Constructed_Value);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATETIME)
+ case BACNET_APPLICATION_TAG_DATETIME:
+ /* datetime[2] BACnetDateTime */
+ apdu_len =
+ bacapp_encode_context_datetime(apdu, 2, &value->type.Date_Time);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ case BACNET_APPLICATION_TAG_LIGHTING_COMMAND:
+ apdu_len = lighting_command_encode_context(
+ apdu, 3, &value->type.Lighting_Command);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Decode a BACnetTimerStateChangeValue
+ * @param apdu - the APDU buffer
+ * @param apdu_size - the APDU buffer size
+ * @param tag_data_type - application tag type expected
+ * @param len_value_type - the length or value type of the tag value content.
+ * @param value - the value where the decoded data is copied into
+ * @return length of the APDU buffer decoded, or BACNET_STATUS_ERROR
+ */
+int bacnet_timer_value_type_decode(
+ const uint8_t *apdu,
+ size_t apdu_size,
+ uint8_t tag_data_type,
+ uint32_t len_value_type,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value)
+{
+ int len = 0;
+
+ if (!apdu) {
+ return BACNET_STATUS_ERROR;
+ }
+ if (!value) {
+ return BACNET_STATUS_ERROR;
+ }
+ switch (tag_data_type) {
+ case BACNET_APPLICATION_TAG_NULL:
+ /* nothing else to do */
+ break;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ case BACNET_APPLICATION_TAG_BOOLEAN:
+ value->type.Boolean = decode_boolean(len_value_type);
+ len = 0;
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ case BACNET_APPLICATION_TAG_UNSIGNED_INT:
+ len = bacnet_unsigned_decode(
+ apdu, apdu_size, len_value_type, &value->type.Unsigned_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ case BACNET_APPLICATION_TAG_SIGNED_INT:
+ len = bacnet_signed_decode(
+ apdu, apdu_size, len_value_type, &value->type.Signed_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ case BACNET_APPLICATION_TAG_REAL:
+ len = bacnet_real_decode(
+ apdu, apdu_size, len_value_type, &(value->type.Real));
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ case BACNET_APPLICATION_TAG_DOUBLE:
+ len = bacnet_double_decode(
+ apdu, apdu_size, len_value_type, &(value->type.Double));
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_OCTET_STRING)
+ case BACNET_APPLICATION_TAG_OCTET_STRING:
+ len = bacnet_octet_string_decode(
+ apdu, apdu_size, len_value_type, &value->type.Octet_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_CHARACTER_STRING)
+ case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ len = bacnet_character_string_decode(
+ apdu, apdu_size, len_value_type, &value->type.Character_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_BIT_STRING)
+ case BACNET_APPLICATION_TAG_BIT_STRING:
+ len = bacnet_bitstring_decode(
+ apdu, apdu_size, len_value_type, &value->type.Bit_String);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_ENUMERATED)
+ case BACNET_APPLICATION_TAG_ENUMERATED:
+ len = bacnet_enumerated_decode(
+ apdu, apdu_size, len_value_type, &value->type.Enumerated);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATE)
+ case BACNET_APPLICATION_TAG_DATE:
+ len = bacnet_date_decode(
+ apdu, apdu_size, len_value_type, &value->type.Date);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_TIME)
+ case BACNET_APPLICATION_TAG_TIME:
+ len = bacnet_time_decode(
+ apdu, apdu_size, len_value_type, &value->type.Time);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_OBJECT_ID)
+ case BACNET_APPLICATION_TAG_OBJECT_ID:
+ len = bacnet_object_id_decode(
+ apdu, apdu_size, len_value_type, &value->type.Object_Id.type,
+ &value->type.Object_Id.instance);
+ break;
+#endif
+ default:
+ len = BACNET_STATUS_ERROR;
+ break;
+ }
+ if ((len == 0) && (tag_data_type != BACNET_APPLICATION_TAG_NULL) &&
+ (tag_data_type != BACNET_APPLICATION_TAG_BOOLEAN) &&
+ (tag_data_type != BACNET_APPLICATION_TAG_OCTET_STRING)) {
+ /* indicate that we were not able to decode the value */
+ len = BACNET_STATUS_ERROR;
+ }
+ if (len != BACNET_STATUS_ERROR) {
+ value->tag = tag_data_type;
+ }
+
+ return len;
+}
+
+/**
+ * @brief Encode a given BACnetTimerStateChangeValue
+ * @param apdu - APDU buffer for storing the encoded data, or NULL for length
+ * @param apdu_size - size of the APDU buffer
+ * @param value - BACNET_TIMER_STATE_CHANGE_VALUE value
+ * @return number of bytes in the APDU, or BACNET_STATUS_ERROR
+ */
+int bacnet_timer_value_encode(
+ uint8_t *apdu,
+ size_t apdu_size,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value)
+{
+ size_t apdu_len = 0; /* total length of the apdu, return value */
+
+ apdu_len = bacnet_timer_value_type_encode(NULL, value);
+ if (apdu_len > apdu_size) {
+ apdu_len = 0;
+ } else {
+ apdu_len = bacnet_timer_value_type_encode(apdu, value);
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Decode a given BACnetTimerStateChangeValue
+ * @param apdu - APDU buffer for decoding
+ * @param apdu_size - Count of valid bytes in the buffer
+ * @param value - BACNET_TIMER_STATE_CHANGE_VALUE value to store the decoded
+ * data
+ * @return number of bytes decoded or BACNET_STATUS_ERROR on error
+ */
+int bacnet_timer_value_decode(
+ const uint8_t *apdu,
+ size_t apdu_size,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value)
+{
+ int len = 0, tag_len = 0;
+ int apdu_len = 0;
+ BACNET_TAG tag = { 0 };
+
+ if (!apdu) {
+ return BACNET_STATUS_ERROR;
+ }
+ if (!value) {
+ return BACNET_STATUS_ERROR;
+ }
+ tag_len = bacnet_tag_decode(&apdu[apdu_len], apdu_size - apdu_len, &tag);
+ if (tag_len > 0) {
+ apdu_len += tag_len;
+ if (tag.application) {
+ len = bacnet_timer_value_type_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, tag.number,
+ tag.len_value_type, value);
+ if (len >= 0) {
+ apdu_len += len;
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ } else if (tag.opening) {
+ switch (tag.number) {
+ case 0:
+ /* no-value[0] Null */
+ value->tag = BACNET_APPLICATION_TAG_NO_VALUE;
+ if (tag.len_value_type == 0) {
+ len = tag_len;
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ break;
+ case 1:
+ /* constructed-value[1] ABSTRACT-SYNTAX.&Type */
+ value->tag = BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX;
+#if defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE)
+ len = bacnet_enclosed_data_length(apdu, apdu_size);
+ len = bacnet_constructed_value_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, len,
+ &value->type.Constructed_Value);
+#endif
+ break;
+ case 2:
+ /* datetime[2] BACnetDateTime */
+ value->tag = BACNET_APPLICATION_TAG_DATETIME;
+#if defined(BACNET_TIMER_VALUE_DATETIME)
+ len = bacnet_datetime_decode(
+ &apdu[apdu_len], apdu_size - apdu_len,
+ &value->type.Date_Time);
+#endif
+ break;
+ case 3:
+ /* lighting-command[3] BACnetLightingCommand */
+ value->tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND;
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ len = lighting_command_decode(
+ &apdu[apdu_len], apdu_size - apdu_len,
+ &value->type.Lighting_Command);
+#endif
+ break;
+ default:
+ return BACNET_STATUS_ERROR;
+ }
+ if (len > 0) {
+ apdu_len += len;
+ if (bacnet_is_closing_tag_number(
+ &apdu[apdu_len], apdu_size - apdu_len, tag.number,
+ &len)) {
+ apdu_len += len;
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+ } else {
+ return BACNET_STATUS_ERROR;
+ }
+
+ return apdu_len;
+}
+
+/**
+ * @brief Compare two BACnetTimerStateChangeValue values
+ * @param value1 [in] The first BACnetTimerStateChangeValue value
+ * @param value2 [in] The second BACnetTimerStateChangeValue value
+ * @return True if the values are the same, else False
+ */
+bool bacnet_timer_value_same(
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value1,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value2)
+{
+ if (value1->tag != value2->tag) {
+ return false;
+ }
+ switch (value1->tag) {
+ case BACNET_APPLICATION_TAG_NULL:
+ return true;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ case BACNET_APPLICATION_TAG_BOOLEAN:
+ return value1->type.Boolean == value2->type.Boolean;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ case BACNET_APPLICATION_TAG_UNSIGNED_INT:
+ return value1->type.Unsigned_Int == value2->type.Unsigned_Int;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ case BACNET_APPLICATION_TAG_SIGNED_INT:
+ return value1->type.Signed_Int == value2->type.Signed_Int;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ case BACNET_APPLICATION_TAG_REAL:
+ return !islessgreater(value1->type.Real, value2->type.Real);
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ case BACNET_APPLICATION_TAG_DOUBLE:
+ return !islessgreater(value1->type.Double, value2->type.Double);
+#endif
+#if defined(BACNET_TIMER_VALUE_OCTET_STRING)
+ case BACNET_APPLICATION_TAG_OCTET_STRING:
+ return octetstring_value_same(
+ &value1->type.Octet_String, &value2->type.Octet_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_CHARACTER_STRING)
+ case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ return characterstring_same(
+ &value1->type.Character_String, &value2->type.Character_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_BIT_STRING)
+ case BACNET_APPLICATION_TAG_BIT_STRING:
+ return bitstring_same(
+ &value1->type.Bit_String, &value2->type.Bit_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_ENUMERATED)
+ case BACNET_APPLICATION_TAG_ENUMERATED:
+ return value1->type.Enumerated == value2->type.Enumerated;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATE)
+ case BACNET_APPLICATION_TAG_DATE:
+ if (datetime_compare_date(&value1->type.Date, &value2->type.Date) ==
+ 0) {
+ return true;
+ }
+ return false;
+#endif
+#if defined(BACNET_TIMER_VALUE_TIME)
+ case BACNET_APPLICATION_TAG_TIME:
+ if (datetime_compare_time(&value1->type.Time, &value2->type.Time) ==
+ 0) {
+ return true;
+ }
+ return false;
+#endif
+#if defined(BACNET_TIMER_VALUE_OBJECT_ID)
+ case BACNET_APPLICATION_TAG_OBJECT_ID:
+ return bacnet_object_id_same(
+ value1->type.Object_Id.type, value1->type.Object_Id.instance,
+ value2->type.Object_Id.type, value2->type.Object_Id.instance);
+#endif
+#if defined(BACNET_TIMER_VALUE_DATETIME)
+ case BACNET_APPLICATION_TAG_DATETIME:
+ return (
+ datetime_compare(
+ &value1->type.Date_Time, &value2->type.Date_Time) == 0);
+#endif
+#if defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE)
+ case BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX:
+ return bacnet_constructed_value_same(
+ &value1->type.Constructed_Value,
+ &value2->type.Constructed_Value);
+#endif
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ case BACNET_APPLICATION_TAG_LIGHTING_COMMAND:
+ return lighting_command_same(
+ &value1->type.Lighting_Command, &value2->type.Lighting_Command);
+#endif
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/**
+ * @brief Copy a BACnetTimerStateChangeValue to another
+ * @param value1 [in] The first BACnetTimerStateChangeValue value
+ * @param value2 [in] The second BACnetTimerStateChangeValue value
+ * @return true if the value was copied, else false
+ */
+bool bacnet_timer_value_copy(
+ BACNET_TIMER_STATE_CHANGE_VALUE *dest,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *src)
+{
+ if (!dest || !src) {
+ return false;
+ }
+ dest->tag = src->tag;
+ switch (src->tag) {
+ case BACNET_APPLICATION_TAG_NULL:
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ return true;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ case BACNET_APPLICATION_TAG_BOOLEAN:
+ dest->type.Boolean = src->type.Boolean;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ case BACNET_APPLICATION_TAG_UNSIGNED_INT:
+ dest->type.Unsigned_Int = src->type.Unsigned_Int;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ case BACNET_APPLICATION_TAG_SIGNED_INT:
+ dest->type.Signed_Int = src->type.Signed_Int;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ case BACNET_APPLICATION_TAG_REAL:
+ dest->type.Real = src->type.Real;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ case BACNET_APPLICATION_TAG_DOUBLE:
+ dest->type.Double = src->type.Double;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_OCTET_STRING)
+ case BACNET_APPLICATION_TAG_OCTET_STRING:
+ return octetstring_copy(
+ &dest->type.Octet_String, &src->type.Octet_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_CHARACTER_STRING)
+ case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ return characterstring_copy(
+ &dest->type.Character_String, &src->type.Character_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_BIT_STRING)
+ case BACNET_APPLICATION_TAG_BIT_STRING:
+ return bitstring_copy(
+ &dest->type.Bit_String, &src->type.Bit_String);
+#endif
+#if defined(BACNET_TIMER_VALUE_ENUMERATED)
+ case BACNET_APPLICATION_TAG_ENUMERATED:
+ dest->type.Enumerated = src->type.Enumerated;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATE)
+ case BACNET_APPLICATION_TAG_DATE:
+ datetime_copy_date(&dest->type.Date, &src->type.Date);
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_TIME)
+ case BACNET_APPLICATION_TAG_TIME:
+ datetime_copy_time(&dest->type.Time, &src->type.Time);
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_OBJECT_ID)
+ case BACNET_APPLICATION_TAG_OBJECT_ID:
+ dest->type.Object_Id.type = src->type.Object_Id.type;
+ dest->type.Object_Id.instance = src->type.Object_Id.instance;
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATETIME)
+ case BACNET_APPLICATION_TAG_DATETIME:
+ datetime_copy(&dest->type.Date_Time, &src->type.Date_Time);
+ return true;
+#endif
+#if defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE)
+ case BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX:
+ return bacnet_constructed_value_copy(
+ &dest->type.Constructed_Value, &src->type.Constructed_Value);
+#endif
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ case BACNET_APPLICATION_TAG_LIGHTING_COMMAND:
+ return lighting_command_copy(
+ &dest->type.Lighting_Command, &src->type.Lighting_Command);
+#endif
+ default:
+ break;
+ }
+
+ return false;
+}
+
+/**
+ * @brief Parse a string into a BACnetTimerStateChangeValue structure
+ * @param value [out] The BACnetTimerStateChangeValue value
+ * @param argv [in] The string to parse
+ * @return true on success, else false
+ */
+bool bacnet_timer_value_from_ascii(
+ BACNET_TIMER_STATE_CHANGE_VALUE *value, const char *argv)
+{
+ bool status = false;
+ int count;
+ unsigned long unsigned_value;
+ long signed_value;
+ float single_value;
+ double double_value;
+ const char *negative;
+ const char *decimal_point;
+ const char *lighting_command;
+ const char *real_string;
+ const char *double_string;
+
+ if (!value || !argv) {
+ return false;
+ }
+ if (!status) {
+ if (bacnet_stricmp(argv, "null") == 0) {
+ value->tag = BACNET_APPLICATION_TAG_NULL;
+ status = true;
+ }
+ }
+ if (!status) {
+ status = bacnet_timer_value_no_value_from_ascii(&value->tag, argv);
+ }
+ if (!status) {
+ if (bacnet_stricmp(argv, "true") == 0) {
+ value->tag = BACNET_APPLICATION_TAG_BOOLEAN;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ value->type.Boolean = true;
+#endif
+ status = true;
+ }
+ }
+ if (!status) {
+ if (bacnet_stricmp(argv, "false") == 0) {
+ value->tag = BACNET_APPLICATION_TAG_BOOLEAN;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ value->type.Boolean = false;
+#endif
+ status = true;
+ }
+ }
+ if (!status) {
+ lighting_command = strchr(argv, 'L');
+ if (!lighting_command) {
+ lighting_command = strchr(argv, 'l');
+ }
+ if (lighting_command) {
+ value->tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND;
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ status = lighting_command_from_ascii(
+ &value->type.Lighting_Command, argv + 1);
+#endif
+ }
+ }
+ if (!status) {
+ real_string = strchr(argv, 'F');
+ if (!real_string) {
+ real_string = strchr(argv, 'f');
+ }
+ if (real_string) {
+ value->tag = BACNET_APPLICATION_TAG_REAL;
+ count = sscanf(argv + 1, "%f", &single_value);
+ if (count == 1) {
+#if defined(BACNET_TIMER_VALUE_REAL)
+ value->type.Real = single_value;
+#endif
+ status = true;
+ }
+ }
+ }
+ if (!status) {
+ double_string = strchr(argv, 'D');
+ if (!double_string) {
+ double_string = strchr(argv, 'd');
+ }
+ if (double_string) {
+ count = sscanf(argv + 1, "%lf", &double_value);
+ if (count == 1) {
+ value->tag = BACNET_APPLICATION_TAG_DOUBLE;
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ value->type.Double = double_value;
+#endif
+ status = true;
+ }
+ }
+ }
+ if (!status) {
+ decimal_point = strchr(argv, '.');
+ if (decimal_point) {
+ count = sscanf(argv, "%lf", &double_value);
+ if (count == 1) {
+ if (isgreaterequal(double_value, -FLT_MAX) &&
+ islessequal(double_value, FLT_MAX)) {
+ value->tag = BACNET_APPLICATION_TAG_REAL;
+#if defined(BACNET_TIMER_VALUE_REAL)
+ value->type.Real = (float)double_value;
+#endif
+ } else {
+ value->tag = BACNET_APPLICATION_TAG_DOUBLE;
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ value->type.Double = double_value;
+#endif
+ }
+ status = true;
+ }
+ }
+ }
+ if (!status) {
+ negative = strchr(argv, '-');
+ if (negative) {
+ count = sscanf(argv, "%ld", &signed_value);
+ if (count == 1) {
+ value->tag = BACNET_APPLICATION_TAG_SIGNED_INT;
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ value->type.Signed_Int = signed_value;
+#endif
+ status = true;
+ }
+ }
+ }
+ if (!status) {
+ count = sscanf(argv, "%lu", &unsigned_value);
+ if (count == 1) {
+ value->tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ value->type.Unsigned_Int = unsigned_value;
+#endif
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * @brief Produce a string from a BACnetTimerStateChangeValue structure
+ * @param value [in] The BACnetTimerStateChangeValue value
+ * @param str [out] The string to produce, NULL to get length only
+ * @param str_size [in] The size of the string buffer
+ * @return length of the produced string
+ */
+int bacnet_timer_value_to_ascii(
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value, char *str, size_t str_size)
+{
+ int str_len = 0;
+
+ if (!value) {
+ return 0;
+ }
+ switch (value->tag) {
+ case BACNET_APPLICATION_TAG_NULL:
+ str_len = snprintf(str, str_size, "null");
+ break;
+ case BACNET_APPLICATION_TAG_NO_VALUE:
+ str_len = bacnet_timer_value_no_value_to_ascii(str, str_size);
+ break;
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ case BACNET_APPLICATION_TAG_BOOLEAN:
+ if (value->type.Boolean) {
+ str_len = snprintf(str, str_size, "true");
+ } else {
+ str_len = snprintf(str, str_size, "false");
+ }
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ case BACNET_APPLICATION_TAG_UNSIGNED_INT:
+ str_len = snprintf(
+ str, str_size, "%lu", (unsigned long)value->type.Unsigned_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ case BACNET_APPLICATION_TAG_SIGNED_INT:
+ str_len =
+ snprintf(str, str_size, "%ld", (long)value->type.Signed_Int);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ case BACNET_APPLICATION_TAG_REAL:
+ str_len = snprintf(str, str_size, "%f", (double)value->type.Real);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ case BACNET_APPLICATION_TAG_DOUBLE:
+ str_len = snprintf(str, str_size, "%f", value->type.Double);
+ break;
+#endif
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ case BACNET_APPLICATION_TAG_LIGHTING_COMMAND:
+ str_len = lighting_command_to_ascii(
+ &value->type.Lighting_Command, str, str_size);
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return str_len;
+}
+
+/**
+ * @brief Convert an array of BACnetTimerStateChangeValue to linked list
+ * @param array pointer to element zero of the array
+ * @param size number of elements in the array
+ */
+void bacnet_timer_value_link_array(
+ BACNET_TIMER_STATE_CHANGE_VALUE *array, size_t size)
+{
+ size_t i = 0;
+
+ for (i = 0; i < size; i++) {
+ if (i > 0) {
+ array[i - 1].next = &array[i];
+ }
+ array[i].next = NULL;
+ }
+}
diff --git a/src/bacnet/timer_value.h b/src/bacnet/timer_value.h
new file mode 100644
index 00000000..14a8b8e1
--- /dev/null
+++ b/src/bacnet/timer_value.h
@@ -0,0 +1,180 @@
+/**
+ * @file
+ * @brief BACnetTimerStateChangeValue data type encoding and decoding API
+ * @author Steve Karg
+ * @date October 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#ifndef BACNET_TIMER_VALUE_H
+#define BACNET_TIMER_VALUE_H
+
+#include
+#include
+#include
+/* BACnet Stack defines - first */
+#include "bacnet/bacdef.h"
+#include "bacnet/bacstr.h"
+#include "bacnet/bacdcode.h"
+#include "bacnet/bacint.h"
+#include "bacnet/datetime.h"
+#include "bacnet/lighting.h"
+
+/* BACNET_TIMER_STATE_CHANGE_VALUE decodes WriteProperty service requests
+ Define the datatypes that your application supports */
+#if !(/* check for any defines, and if none, use NUMERIC */ \
+ defined(BACNET_TIMER_VALUE_NUMERIC) || \
+ defined(BACNET_TIMER_VALUE_ALL) || defined(BACNET_TIMER_VALUE_NULL) || \
+ defined(BACNET_TIMER_VALUE_BOOLEAN) || \
+ defined(BACNET_TIMER_VALUE_UNSIGNED) || \
+ defined(BACNET_TIMER_VALUE_SIGNED) || \
+ defined(BACNET_TIMER_VALUE_REAL) || \
+ defined(BACNET_TIMER_VALUE_DOUBLE) || \
+ defined(BACNET_TIMER_VALUE_OCTET_STRING) || \
+ defined(BACNET_TIMER_VALUE_CHARACTER_STRING) || \
+ defined(BACNET_TIMER_VALUE_BIT_STRING) || \
+ defined(BACNET_TIMER_VALUE_ENUMERATED) || \
+ defined(BACNET_TIMER_VALUE_DATE) || defined(BACNET_TIMER_VALUE_TIME) || \
+ defined(BACNET_TIMER_VALUE_OBJECT_ID) || \
+ defined(BACNET_TIMER_VALUE_NO_VALUE) || \
+ defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE) || \
+ defined(BACNET_TIMER_VALUE_DATETIME) || \
+ defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND))
+#define BACNET_TIMER_VALUE_NUMERIC
+#elif defined(BACNET_TIMER_VALUE_ALL)
+#define BACNET_TIMER_VALUE_NUMERIC
+#define BACNET_TIMER_VALUE_OCTET_STRING
+#define BACNET_TIMER_VALUE_CHARACTER_STRING
+#define BACNET_TIMER_VALUE_BIT_STRING
+#define BACNET_TIMER_VALUE_DATE
+#define BACNET_TIMER_VALUE_TIME
+#define BACNET_TIMER_VALUE_OBJECT_ID
+#define BACNET_TIMER_VALUE_DATETIME
+#define BACNET_TIMER_VALUE_CONSTRUCTED_VALUE
+#define BACNET_TIMER_VALUE_LIGHTING_COMMAND
+#endif
+
+#if defined(BACNET_TIMER_VALUE_NUMERIC)
+#define BACNET_TIMER_VALUE_NULL
+#define BACNET_TIMER_VALUE_BOOLEAN
+#define BACNET_TIMER_VALUE_UNSIGNED
+#define BACNET_TIMER_VALUE_SIGNED
+#define BACNET_TIMER_VALUE_REAL
+#define BACNET_TIMER_VALUE_DOUBLE
+#define BACNET_TIMER_VALUE_ENUMERATED
+#define BACNET_TIMER_VALUE_NO_VALUE
+#endif
+
+typedef struct BACnet_Timer_State_Change_Value_T {
+ uint8_t tag;
+ union {
+ /* null - no type value needed. It is encoded in the tag alone. */
+ /* no-value - no type value needed. It is encoded in the tag alone. */
+#if defined(BACNET_TIMER_VALUE_BOOLEAN)
+ bool Boolean;
+#endif
+#if defined(BACNET_TIMER_VALUE_UNSIGNED)
+ BACNET_UNSIGNED_INTEGER Unsigned_Int;
+#endif
+#if defined(BACNET_TIMER_VALUE_SIGNED)
+ int32_t Signed_Int;
+#endif
+#if defined(BACNET_TIMER_VALUE_REAL)
+ float Real;
+#endif
+#if defined(BACNET_TIMER_VALUE_DOUBLE)
+ double Double;
+#endif
+#if defined(BACNET_TIMER_VALUE_OCTET_STRING)
+ BACNET_OCTET_STRING Octet_String;
+#endif
+#if defined(BACNET_TIMER_VALUE_CHARACTER_STRING)
+ BACNET_CHARACTER_STRING Character_String;
+#endif
+#if defined(BACNET_TIMER_VALUE_BIT_STRING)
+ BACNET_BIT_STRING Bit_String;
+#endif
+#if defined(BACNET_TIMER_VALUE_ENUMERATED)
+ uint32_t Enumerated;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATE)
+ BACNET_DATE Date;
+#endif
+#if defined(BACNET_TIMER_VALUE_TIME)
+ BACNET_TIME Time;
+#endif
+#if defined(BACNET_TIMER_VALUE_OBJECT_ID)
+ BACNET_OBJECT_ID Object_Id;
+#endif
+#if defined(BACNET_TIMER_VALUE_DATETIME)
+ BACNET_DATE_TIME Date_Time;
+#endif
+#if defined(BACNET_TIMER_VALUE_CONSTRUCTED_VALUE)
+ BACNET_CONSTRUCTED_VALUE_TYPE Constructed_Value;
+#endif
+#if defined(BACNET_TIMER_VALUE_LIGHTING_COMMAND)
+ BACNET_LIGHTING_COMMAND Lighting_Command;
+#endif
+ } type;
+ /* simple linked list if needed */
+ struct BACnet_Timer_State_Change_Value_T *next;
+} BACNET_TIMER_STATE_CHANGE_VALUE;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+BACNET_STACK_EXPORT
+int bacnet_timer_value_no_value_encode(uint8_t *apdu);
+BACNET_STACK_EXPORT
+int bacnet_timer_value_no_value_decode(const uint8_t *apdu, uint32_t apdu_size);
+BACNET_STACK_EXPORT
+int bacnet_timer_value_no_value_to_ascii(char *str, size_t str_len);
+BACNET_STACK_EXPORT
+bool bacnet_timer_value_no_value_from_ascii(uint8_t *tag, const char *argv);
+
+BACNET_STACK_EXPORT
+int bacnet_timer_value_type_encode(
+ uint8_t *apdu, const BACNET_TIMER_STATE_CHANGE_VALUE *value);
+BACNET_STACK_EXPORT
+int bacnet_timer_value_type_decode(
+ const uint8_t *apdu,
+ size_t apdu_size,
+ uint8_t tag_data_type,
+ uint32_t len_value_type,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value);
+
+BACNET_STACK_EXPORT
+int bacnet_timer_value_decode(
+ const uint8_t *apdu,
+ size_t apdu_len,
+ BACNET_TIMER_STATE_CHANGE_VALUE *value);
+BACNET_STACK_EXPORT
+int bacnet_timer_value_encode(
+ uint8_t *apdu,
+ size_t apdu_size,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value);
+
+BACNET_STACK_EXPORT
+bool bacnet_timer_value_from_ascii(
+ BACNET_TIMER_STATE_CHANGE_VALUE *value, const char *argv);
+BACNET_STACK_EXPORT
+int bacnet_timer_value_to_ascii(
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value, char *str, size_t str_len);
+
+BACNET_STACK_EXPORT
+bool bacnet_timer_value_copy(
+ BACNET_TIMER_STATE_CHANGE_VALUE *dest,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *src);
+BACNET_STACK_EXPORT
+bool bacnet_timer_value_same(
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value1,
+ const BACNET_TIMER_STATE_CHANGE_VALUE *value2);
+
+BACNET_STACK_EXPORT
+void bacnet_timer_value_link_array(
+ BACNET_TIMER_STATE_CHANGE_VALUE *array, size_t size);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
diff --git a/src/bacnet/timestamp.c b/src/bacnet/timestamp.c
index b1d6680f..98b348bb 100644
--- a/src/bacnet/timestamp.c
+++ b/src/bacnet/timestamp.c
@@ -13,8 +13,6 @@
#include "bacnet/datetime.h"
#include "bacnet/timestamp.h"
-/** @file timestamp.c Encode/Decode BACnet Timestamps */
-
/** Set the sequence number in a timestamp structure.
*
* @param dest Pointer to the destination time stamp structure.
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 3f4de766..d5845a28 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -116,6 +116,7 @@ list(APPEND testdirs
bacnet/rpm
bacnet/secure_connect
bacnet/specialevent
+ bacnet/timer_value
bacnet/timestamp
bacnet/timesync
bacnet/weeklyschedule
@@ -177,6 +178,7 @@ list(APPEND testdirs
bacnet/basic/object/schedule
bacnet/basic/object/structured_view
bacnet/basic/object/time_value
+ bacnet/basic/object/timer
bacnet/basic/object/trendlog
# basic/program
bacnet/basic/program/ubasic
diff --git a/test/bacnet/access_rule/CMakeLists.txt b/test/bacnet/access_rule/CMakeLists.txt
index 0eaa84b9..8ad3aee3 100644
--- a/test/bacnet/access_rule/CMakeLists.txt
+++ b/test/bacnet/access_rule/CMakeLists.txt
@@ -56,6 +56,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/bacapp/CMakeLists.txt b/test/bacnet/bacapp/CMakeLists.txt
index 18e0dc64..29e28d36 100644
--- a/test/bacnet/bacapp/CMakeLists.txt
+++ b/test/bacnet/bacapp/CMakeLists.txt
@@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/datalink/bvlc.c
diff --git a/test/bacnet/bacapp/src/main.c b/test/bacnet/bacapp/src/main.c
index 3a90cb51..377dd5f3 100644
--- a/test/bacnet/bacapp/src/main.c
+++ b/test/bacnet/bacapp/src/main.c
@@ -72,7 +72,15 @@ static const BACNET_APPLICATION_TAG tag_list[] = {
/* BACnetShedLevel */
BACNET_APPLICATION_TAG_SHED_LEVEL,
/* BACnetAccessRule */
- BACNET_APPLICATION_TAG_ACCESS_RULE
+ BACNET_APPLICATION_TAG_ACCESS_RULE,
+ /* BACnetChannelValue */
+ BACNET_APPLICATION_TAG_CHANNEL_VALUE,
+ /* BACnetLogRecord */
+ BACNET_APPLICATION_TAG_LOG_RECORD,
+ /* BACnetTimerChangeValue */
+ BACNET_APPLICATION_TAG_TIMER_VALUE,
+ /* no-value - context tagged null */
+ BACNET_APPLICATION_TAG_NO_VALUE
};
/**
diff --git a/test/bacnet/bacaudit/CMakeLists.txt b/test/bacnet/bacaudit/CMakeLists.txt
index 99204cb6..8743ada5 100644
--- a/test/bacnet/bacaudit/CMakeLists.txt
+++ b/test/bacnet/bacaudit/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/timesync.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/bacdest/CMakeLists.txt b/test/bacnet/bacdest/CMakeLists.txt
index fbd22411..53d101cc 100644
--- a/test/bacnet/bacdest/CMakeLists.txt
+++ b/test/bacnet/bacdest/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/timesync.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/bacdevobjpropref/CMakeLists.txt b/test/bacnet/bacdevobjpropref/CMakeLists.txt
index 97523efd..067db5fc 100644
--- a/test/bacnet/bacdevobjpropref/CMakeLists.txt
+++ b/test/bacnet/bacdevobjpropref/CMakeLists.txt
@@ -56,6 +56,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/baclog/CMakeLists.txt b/test/bacnet/baclog/CMakeLists.txt
index 8398eab9..95a34e69 100644
--- a/test/bacnet/baclog/CMakeLists.txt
+++ b/test/bacnet/baclog/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/timesync.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/bactimevalue/CMakeLists.txt b/test/bacnet/bactimevalue/CMakeLists.txt
index 4be4d8e9..efca4a4a 100644
--- a/test/bacnet/bactimevalue/CMakeLists.txt
+++ b/test/bacnet/bactimevalue/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/dailyschedule.c
diff --git a/test/bacnet/basic/binding/address/CMakeLists.txt b/test/bacnet/basic/binding/address/CMakeLists.txt
index 5c449677..29ab5212 100644
--- a/test/bacnet/basic/binding/address/CMakeLists.txt
+++ b/test/bacnet/basic/binding/address/CMakeLists.txt
@@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/basic/object/acc/CMakeLists.txt b/test/bacnet/basic/object/acc/CMakeLists.txt
index 9d8b48ae..b7727df3 100644
--- a/test/bacnet/basic/object/acc/CMakeLists.txt
+++ b/test/bacnet/basic/object/acc/CMakeLists.txt
@@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/access_credential/CMakeLists.txt b/test/bacnet/basic/object/access_credential/CMakeLists.txt
index ed90e11b..2437734a 100644
--- a/test/bacnet/basic/object/access_credential/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_credential/CMakeLists.txt
@@ -56,6 +56,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/access_door/CMakeLists.txt b/test/bacnet/basic/object/access_door/CMakeLists.txt
index 1770cc06..cc14d495 100644
--- a/test/bacnet/basic/object/access_door/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_door/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/access_point/CMakeLists.txt b/test/bacnet/basic/object/access_point/CMakeLists.txt
index 53e0e15e..03b7997b 100644
--- a/test/bacnet/basic/object/access_point/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_point/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/basic/object/access_rights/CMakeLists.txt b/test/bacnet/basic/object/access_rights/CMakeLists.txt
index f181564f..91a69541 100644
--- a/test/bacnet/basic/object/access_rights/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_rights/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/property.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/access_user/CMakeLists.txt b/test/bacnet/basic/object/access_user/CMakeLists.txt
index e0eeb1c3..f96f8f34 100644
--- a/test/bacnet/basic/object/access_user/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_user/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/access_zone/CMakeLists.txt b/test/bacnet/basic/object/access_zone/CMakeLists.txt
index a9ca5c11..f461df0a 100644
--- a/test/bacnet/basic/object/access_zone/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_zone/CMakeLists.txt
@@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/ai/CMakeLists.txt b/test/bacnet/basic/object/ai/CMakeLists.txt
index 4b3c0e76..129e255a 100644
--- a/test/bacnet/basic/object/ai/CMakeLists.txt
+++ b/test/bacnet/basic/object/ai/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/ao/CMakeLists.txt b/test/bacnet/basic/object/ao/CMakeLists.txt
index fdacc383..87925927 100644
--- a/test/bacnet/basic/object/ao/CMakeLists.txt
+++ b/test/bacnet/basic/object/ao/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/auditlog/CMakeLists.txt b/test/bacnet/basic/object/auditlog/CMakeLists.txt
index aad98616..7f75fb81 100644
--- a/test/bacnet/basic/object/auditlog/CMakeLists.txt
+++ b/test/bacnet/basic/object/auditlog/CMakeLists.txt
@@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/av/CMakeLists.txt b/test/bacnet/basic/object/av/CMakeLists.txt
index f2df11e7..cc58356d 100644
--- a/test/bacnet/basic/object/av/CMakeLists.txt
+++ b/test/bacnet/basic/object/av/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/bacfile/CMakeLists.txt b/test/bacnet/basic/object/bacfile/CMakeLists.txt
index 8071bab1..fdec5547 100644
--- a/test/bacnet/basic/object/bacfile/CMakeLists.txt
+++ b/test/bacnet/basic/object/bacfile/CMakeLists.txt
@@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/bi/CMakeLists.txt b/test/bacnet/basic/object/bi/CMakeLists.txt
index 162df733..037a5081 100644
--- a/test/bacnet/basic/object/bi/CMakeLists.txt
+++ b/test/bacnet/basic/object/bi/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
index d6dedb10..809fa625 100644
--- a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
+++ b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
@@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/property.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/blo/CMakeLists.txt b/test/bacnet/basic/object/blo/CMakeLists.txt
index bf2872a9..3f7f929e 100644
--- a/test/bacnet/basic/object/blo/CMakeLists.txt
+++ b/test/bacnet/basic/object/blo/CMakeLists.txt
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/bo/CMakeLists.txt b/test/bacnet/basic/object/bo/CMakeLists.txt
index 25bb39d2..4188b4dc 100644
--- a/test/bacnet/basic/object/bo/CMakeLists.txt
+++ b/test/bacnet/basic/object/bo/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/bv/CMakeLists.txt b/test/bacnet/basic/object/bv/CMakeLists.txt
index 36c5ebba..92a7cce6 100644
--- a/test/bacnet/basic/object/bv/CMakeLists.txt
+++ b/test/bacnet/basic/object/bv/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/calendar/CMakeLists.txt b/test/bacnet/basic/object/calendar/CMakeLists.txt
index 48c3e199..721d41b6 100644
--- a/test/bacnet/basic/object/calendar/CMakeLists.txt
+++ b/test/bacnet/basic/object/calendar/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/channel/CMakeLists.txt b/test/bacnet/basic/object/channel/CMakeLists.txt
index 6ff55e40..1cdbb814 100644
--- a/test/bacnet/basic/object/channel/CMakeLists.txt
+++ b/test/bacnet/basic/object/channel/CMakeLists.txt
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/property.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt
index ac393ac0..4ce7139d 100644
--- a/test/bacnet/basic/object/color_object/CMakeLists.txt
+++ b/test/bacnet/basic/object/color_object/CMakeLists.txt
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/color_temperature/CMakeLists.txt b/test/bacnet/basic/object/color_temperature/CMakeLists.txt
index ecf268cd..34417b3b 100644
--- a/test/bacnet/basic/object/color_temperature/CMakeLists.txt
+++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/command/CMakeLists.txt b/test/bacnet/basic/object/command/CMakeLists.txt
index 7bfa47b4..a3f08c70 100644
--- a/test/bacnet/basic/object/command/CMakeLists.txt
+++ b/test/bacnet/basic/object/command/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/credential_data_input/CMakeLists.txt b/test/bacnet/basic/object/credential_data_input/CMakeLists.txt
index 03ceb9a1..81992a87 100644
--- a/test/bacnet/basic/object/credential_data_input/CMakeLists.txt
+++ b/test/bacnet/basic/object/credential_data_input/CMakeLists.txt
@@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/csv/CMakeLists.txt b/test/bacnet/basic/object/csv/CMakeLists.txt
index c636b720..addebe52 100644
--- a/test/bacnet/basic/object/csv/CMakeLists.txt
+++ b/test/bacnet/basic/object/csv/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt
index 702e0d6b..80754004 100644
--- a/test/bacnet/basic/object/device/CMakeLists.txt
+++ b/test/bacnet/basic/object/device/CMakeLists.txt
@@ -80,6 +80,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/schedule.c
${SRC_DIR}/bacnet/basic/object/structured_view.c
${SRC_DIR}/bacnet/basic/object/time_value.c
+ ${SRC_DIR}/bacnet/basic/object/timer.c
${SRC_DIR}/bacnet/basic/object/trendlog.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
@@ -100,6 +101,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/property.c
${SRC_DIR}/bacnet/reject.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/iv/CMakeLists.txt b/test/bacnet/basic/object/iv/CMakeLists.txt
index 1af78de9..95ec1ee1 100644
--- a/test/bacnet/basic/object/iv/CMakeLists.txt
+++ b/test/bacnet/basic/object/iv/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/lc/CMakeLists.txt b/test/bacnet/basic/object/lc/CMakeLists.txt
index f83246a5..cfc98c34 100644
--- a/test/bacnet/basic/object/lc/CMakeLists.txt
+++ b/test/bacnet/basic/object/lc/CMakeLists.txt
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/rp.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt
index 1da83507..7f1285f6 100644
--- a/test/bacnet/basic/object/lo/CMakeLists.txt
+++ b/test/bacnet/basic/object/lo/CMakeLists.txt
@@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/lsp/CMakeLists.txt b/test/bacnet/basic/object/lsp/CMakeLists.txt
index a9627d2f..a8cbebbe 100644
--- a/test/bacnet/basic/object/lsp/CMakeLists.txt
+++ b/test/bacnet/basic/object/lsp/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/lsz/CMakeLists.txt b/test/bacnet/basic/object/lsz/CMakeLists.txt
index 56b7f41e..13a1eb3e 100644
--- a/test/bacnet/basic/object/lsz/CMakeLists.txt
+++ b/test/bacnet/basic/object/lsz/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/ms-input/CMakeLists.txt b/test/bacnet/basic/object/ms-input/CMakeLists.txt
index beb0b686..d2a78957 100644
--- a/test/bacnet/basic/object/ms-input/CMakeLists.txt
+++ b/test/bacnet/basic/object/ms-input/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/mso/CMakeLists.txt b/test/bacnet/basic/object/mso/CMakeLists.txt
index 2d94fa04..6f62197c 100644
--- a/test/bacnet/basic/object/mso/CMakeLists.txt
+++ b/test/bacnet/basic/object/mso/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/msv/CMakeLists.txt b/test/bacnet/basic/object/msv/CMakeLists.txt
index 09def1c7..dc3d865d 100644
--- a/test/bacnet/basic/object/msv/CMakeLists.txt
+++ b/test/bacnet/basic/object/msv/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/nc/CMakeLists.txt b/test/bacnet/basic/object/nc/CMakeLists.txt
index 9381cce6..73a6c6a7 100644
--- a/test/bacnet/basic/object/nc/CMakeLists.txt
+++ b/test/bacnet/basic/object/nc/CMakeLists.txt
@@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/list_element.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/nc/src/main.c b/test/bacnet/basic/object/nc/src/main.c
index 69078c29..351da132 100644
--- a/test/bacnet/basic/object/nc/src/main.c
+++ b/test/bacnet/basic/object/nc/src/main.c
@@ -285,9 +285,9 @@ static void test_Notification_Class_Recipient_List(void)
list_element.first_failed_element_number = 0;
err = Notification_Class_Add_List_Element(&list_element);
zassert_equal(err, BACNET_STATUS_ERROR, NULL);
- zassert_equal(list_element.error_class, ERROR_CLASS_SERVICES, NULL);
+ zassert_equal(list_element.error_class, ERROR_CLASS_PROPERTY, NULL);
zassert_equal(
- list_element.error_code, ERROR_CODE_PROPERTY_IS_NOT_A_LIST, NULL);
+ list_element.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL);
/* valid element, valid property, array element (object is not an array) */
list_element.object_property = PROP_RECIPIENT_LIST;
list_element.array_index = 0;
@@ -346,9 +346,9 @@ static void test_Notification_Class_Recipient_List(void)
list_element.object_property = PROP_ALL;
err = Notification_Class_Remove_List_Element(&list_element);
zassert_equal(err, BACNET_STATUS_ERROR, NULL);
- zassert_equal(list_element.error_class, ERROR_CLASS_SERVICES, NULL);
+ zassert_equal(list_element.error_class, ERROR_CLASS_PROPERTY, NULL);
zassert_equal(
- list_element.error_code, ERROR_CODE_PROPERTY_IS_NOT_A_LIST, NULL);
+ list_element.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL);
/* invalid array */
list_element.object_property = PROP_RECIPIENT_LIST;
list_element.array_index = 0;
diff --git a/test/bacnet/basic/object/netport/CMakeLists.txt b/test/bacnet/basic/object/netport/CMakeLists.txt
index 9f818a73..6d19dc4d 100644
--- a/test/bacnet/basic/object/netport/CMakeLists.txt
+++ b/test/bacnet/basic/object/netport/CMakeLists.txt
@@ -70,6 +70,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/property.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/osv/CMakeLists.txt b/test/bacnet/basic/object/osv/CMakeLists.txt
index 20ec6946..803237c2 100644
--- a/test/bacnet/basic/object/osv/CMakeLists.txt
+++ b/test/bacnet/basic/object/osv/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/piv/CMakeLists.txt b/test/bacnet/basic/object/piv/CMakeLists.txt
index c6bb6d09..1bc61ecc 100644
--- a/test/bacnet/basic/object/piv/CMakeLists.txt
+++ b/test/bacnet/basic/object/piv/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/program/CMakeLists.txt b/test/bacnet/basic/object/program/CMakeLists.txt
index 7290df8c..8f848e43 100644
--- a/test/bacnet/basic/object/program/CMakeLists.txt
+++ b/test/bacnet/basic/object/program/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/schedule/CMakeLists.txt b/test/bacnet/basic/object/schedule/CMakeLists.txt
index 7d188562..5e4103f5 100644
--- a/test/bacnet/basic/object/schedule/CMakeLists.txt
+++ b/test/bacnet/basic/object/schedule/CMakeLists.txt
@@ -56,6 +56,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/structured_view/CMakeLists.txt b/test/bacnet/basic/object/structured_view/CMakeLists.txt
index 7ef0e3a0..9e14de55 100644
--- a/test/bacnet/basic/object/structured_view/CMakeLists.txt
+++ b/test/bacnet/basic/object/structured_view/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/property.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/object/test/property_test.c b/test/bacnet/basic/object/test/property_test.c
index ac641a43..06aa2221 100644
--- a/test/bacnet/basic/object/test/property_test.c
+++ b/test/bacnet/basic/object/test/property_test.c
@@ -227,7 +227,9 @@ int bacnet_object_property_read_test(
apdu_len = read_len;
len =
bacnet_unsigned_application_decode(apdu, apdu_len, &array_size);
- zassert_true(len > 0, NULL);
+ zassert_true(
+ len > 0, "property '%s' array_index=0\n",
+ bactext_property_name(rpdata->object_property));
zassert_true(
len == read_len, "property '%s' array_index=0.\n",
bactext_property_name(rpdata->object_property));
@@ -243,6 +245,19 @@ int bacnet_object_property_read_test(
}
}
}
+ /* test an array index that is likely out of range */
+ rpdata->array_index = BACNET_ARRAY_ALL - 1;
+ read_len = read_property(rpdata);
+ zassert_equal(
+ read_len, BACNET_STATUS_ERROR,
+ "property '%s' array_index=%u: error code is %s.\n",
+ bactext_property_name(rpdata->object_property), rpdata->array_index,
+ bactext_error_code_name(rpdata->error_code));
+ zassert_equal(
+ rpdata->error_code, ERROR_CODE_INVALID_ARRAY_INDEX,
+ "property '%s' array_index=%u: error code is %s.\n",
+ bactext_property_name(rpdata->object_property), rpdata->array_index,
+ bactext_error_code_name(rpdata->error_code));
}
return len;
@@ -278,6 +293,9 @@ void bacnet_object_properties_read_write_test(
bool commandable = false;
bool status = false;
+ /* negative test */
+ len = read_property(NULL);
+ zassert_equal(len, 0, NULL);
/* ReadProperty parameters */
rpdata.application_data = &apdu[0];
rpdata.application_data_len = sizeof(apdu);
diff --git a/test/bacnet/basic/object/time_value/CMakeLists.txt b/test/bacnet/basic/object/time_value/CMakeLists.txt
index eeb0cf2f..ef7f4501 100644
--- a/test/bacnet/basic/object/time_value/CMakeLists.txt
+++ b/test/bacnet/basic/object/time_value/CMakeLists.txt
@@ -56,6 +56,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/basic/object/timer/CMakeLists.txt b/test/bacnet/basic/object/timer/CMakeLists.txt
new file mode 100644
index 00000000..4ca4b802
--- /dev/null
+++ b/test/bacnet/basic/object/timer/CMakeLists.txt
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: MIT
+
+cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
+
+get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+project(test_${basename}
+ VERSION 1.0.0
+ LANGUAGES C)
+
+
+string(REGEX REPLACE
+ "/test/bacnet/[a-zA-Z_/-]*$"
+ "/src"
+ SRC_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR})
+string(REGEX REPLACE
+ "/test/bacnet/[a-zA-Z_/-]*$"
+ "/test"
+ TST_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR})
+set(ZTST_DIR "${TST_DIR}/ztest/src")
+
+add_compile_definitions(
+ BIG_ENDIAN=0
+ CONFIG_ZTEST=1
+ )
+
+include_directories(
+ ${SRC_DIR}
+ ${TST_DIR}/bacnet/basic/object/test
+ ${TST_DIR}/ztest/include
+ )
+
+add_executable(${PROJECT_NAME}
+ # File(s) under test
+ ${SRC_DIR}/bacnet/basic/object/timer.c
+ # Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/bacaction.c
+ ${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/baclog.c
+ ${SRC_DIR}/bacnet/bacreal.c
+ ${SRC_DIR}/bacnet/bacstr.c
+ ${SRC_DIR}/bacnet/bactext.c
+ ${SRC_DIR}/bacnet/bactimevalue.c
+ ${SRC_DIR}/bacnet/basic/sys/bigend.c
+ ${SRC_DIR}/bacnet/basic/sys/days.c
+ ${SRC_DIR}/bacnet/basic/sys/debug.c
+ ${SRC_DIR}/bacnet/basic/sys/keylist.c
+ ${SRC_DIR}/bacnet/datetime.c
+ ${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/hostnport.c
+ ${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/property.c
+ ${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
+ ${SRC_DIR}/bacnet/timestamp.c
+ ${SRC_DIR}/bacnet/wp.c
+ ${SRC_DIR}/bacnet/weeklyschedule.c
+ ${SRC_DIR}/bacnet/dailyschedule.c
+ ${SRC_DIR}/bacnet/calendar_entry.c
+ ${SRC_DIR}/bacnet/special_event.c
+ ${SRC_DIR}/bacnet/channel_value.c
+ ${SRC_DIR}/bacnet/secure_connect.c
+ # Test and test library files
+ ./src/main.c
+ ${TST_DIR}/bacnet/basic/object/test/datetime_local.c
+ ${TST_DIR}/bacnet/basic/object/test/device_mock.c
+ ${TST_DIR}/bacnet/basic/object/test/property_test.c
+ ${ZTST_DIR}/ztest_mock.c
+ ${ZTST_DIR}/ztest.c
+ )
diff --git a/test/bacnet/basic/object/timer/src/main.c b/test/bacnet/basic/object/timer/src/main.c
new file mode 100644
index 00000000..56881ccb
--- /dev/null
+++ b/test/bacnet/basic/object/timer/src/main.c
@@ -0,0 +1,722 @@
+/**
+ * @file
+ * @brief Unit test for Timer object
+ * @author Steve Karg
+ * @date October 2025
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * @addtogroup bacnet_tests
+ * @{
+ */
+static BACNET_WRITE_PROPERTY_DATA Write_Property_Internal_Data;
+static bool Write_Property_Internal(BACNET_WRITE_PROPERTY_DATA *wp_data)
+{
+ memcpy(
+ &Write_Property_Internal_Data, wp_data,
+ sizeof(BACNET_WRITE_PROPERTY_DATA));
+
+ return true;
+}
+
+/**
+ * @brief Test
+ */
+static void test_Timer_Read_Write(void)
+{
+ const uint32_t instance = 123;
+ unsigned count = 0, test_count = 0;
+ unsigned index = 0;
+ const char *sample_name = "Timer:0";
+ char *sample_context = "context";
+ const char *sample_description = "Timer Description";
+ const char *test_name = NULL;
+ uint32_t test_instance = 0;
+ bool status = false;
+ const int skip_fail_property_list[] = { -1 };
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 }, *test_member = NULL;
+ BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_CHARACTER_STRING cstring = { 0 };
+ uint8_t apdu[MAX_APDU] = { 0 };
+ BACNET_LIST_ELEMENT_DATA list_element = {
+ .application_data = apdu,
+ .application_data_len = sizeof(apdu),
+ .array_index = BACNET_ARRAY_ALL,
+ .error_class = ERROR_CLASS_PROPERTY,
+ .error_code = ERROR_CODE_SUCCESS,
+ .first_failed_element_number = 0,
+ .object_instance = instance,
+ .object_type = OBJECT_TIMER,
+ .object_property = PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES
+ };
+ int err = 0;
+
+ Timer_Init();
+ Timer_Create(instance);
+ status = Timer_Valid_Instance(instance);
+ zassert_true(status, NULL);
+ status = Timer_Valid_Instance(instance - 1);
+ zassert_false(status, NULL);
+ index = Timer_Instance_To_Index(instance);
+ zassert_equal(index, 0, NULL);
+ test_instance = Timer_Index_To_Instance(index);
+ zassert_equal(instance, test_instance, NULL);
+ count = Timer_Count();
+ zassert_true(count > 0, NULL);
+ /* configure the instance property values and test API for lists */
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_ANALOG_OUTPUT;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Set(instance, index, &member);
+ zassert_true(status, NULL);
+ count = Timer_Reference_List_Member_Element_Count(instance);
+ zassert_equal(count, 1, NULL);
+ /* add the same element - should be success without actually adding */
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ count = Timer_Reference_List_Member_Element_Count(instance);
+ zassert_equal(count, 1, NULL);
+ /* next */
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_BINARY_OUTPUT;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_MULTI_STATE_OUTPUT;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_LIGHTING_OUTPUT;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_COLOR;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_COLOR_TEMPERATURE;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Add(instance, &member);
+ zassert_true(status, NULL);
+ count = Timer_Reference_List_Member_Element_Count(instance);
+ status = Timer_Reference_List_Member_Element_Remove(instance, &member);
+ test_count = Timer_Reference_List_Member_Element_Count(instance);
+ zassert_true(
+ count > test_count, "count=%u test_count=%u", count, test_count);
+ /* reliability and status flags */
+ status = Timer_Reliability_Set(instance, RELIABILITY_PROCESS_ERROR);
+ zassert_true(status, NULL);
+ /* perform a general test for RP/WP */
+ bacnet_object_properties_read_write_test(
+ OBJECT_TIMER, instance, Timer_Property_Lists, Timer_Read_Property,
+ Timer_Write_Property, skip_fail_property_list);
+ /* test the ASCII name get/set */
+ status = Timer_Name_Set(instance, sample_name);
+ zassert_true(status, NULL);
+ test_name = Timer_Name_ASCII(instance);
+ zassert_equal(test_name, sample_name, NULL);
+ status = Timer_Object_Name(instance, &cstring);
+ zassert_true(status, NULL);
+ status = characterstring_ansi_same(&cstring, sample_name);
+ zassert_true(status, NULL);
+ status = Timer_Name_Set(instance, NULL);
+ zassert_true(status, NULL);
+ test_name = Timer_Name_ASCII(instance);
+ zassert_equal(test_name, NULL, NULL);
+
+ /* test specific WriteProperty values - common configuration */
+ wp_data.object_type = OBJECT_TIMER;
+ wp_data.object_instance = instance;
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.priority = BACNET_MAX_PRIORITY;
+ zassert_true(status, NULL);
+ /* out-of-service */
+ wp_data.object_property = PROP_OUT_OF_SERVICE;
+ value.tag = BACNET_APPLICATION_TAG_BOOLEAN;
+ value.type.Boolean = true;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Boolean = false;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 123;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ /* write present-value */
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 0;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ /* configure min-pres-value and max-pres-value to max limits */
+ wp_data.object_property = PROP_MIN_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_MAX_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = UINT32_MAX;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = UINT32_MAX;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ /* configure min-pres-value and max-pres-value to elicit out-of-range */
+ wp_data.object_property = PROP_MIN_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 100;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_MAX_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = UINT32_MAX - 100;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ wp_data.object_property = PROP_PRESENT_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = UINT32_MAX;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* list-of-object-property-references - single element list */
+ wp_data.object_property = PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES;
+ value.tag = BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE;
+ value.type.Device_Object_Property_Reference.arrayIndex = BACNET_ARRAY_ALL;
+ value.type.Device_Object_Property_Reference.deviceIdentifier.type =
+ OBJECT_DEVICE;
+ value.type.Device_Object_Property_Reference.deviceIdentifier.instance =
+ 12345;
+ value.type.Device_Object_Property_Reference.objectIdentifier.type =
+ OBJECT_ANALOG_OUTPUT;
+ value.type.Device_Object_Property_Reference.objectIdentifier.instance = 1;
+ value.type.Device_Object_Property_Reference.propertyIdentifier =
+ PROP_PRESENT_VALUE;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, "%s", bactext_error_code_name(wp_data.error_code));
+ /* add list element */
+ value.tag = BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE;
+ value.type.Device_Object_Property_Reference.arrayIndex = BACNET_ARRAY_ALL;
+ value.type.Device_Object_Property_Reference.deviceIdentifier.type =
+ OBJECT_DEVICE;
+ value.type.Device_Object_Property_Reference.deviceIdentifier.instance =
+ 12345;
+ value.type.Device_Object_Property_Reference.objectIdentifier.type =
+ OBJECT_ANALOG_OUTPUT;
+ value.type.Device_Object_Property_Reference.objectIdentifier.instance = 1;
+ value.type.Device_Object_Property_Reference.propertyIdentifier =
+ PROP_PRESENT_VALUE;
+ list_element.application_data_len =
+ bacapp_encode_application_data(apdu, &value);
+ list_element.object_property = PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES;
+ list_element.array_index = BACNET_ARRAY_ALL;
+ list_element.error_class = ERROR_CLASS_PROPERTY;
+ list_element.error_code = ERROR_CODE_SUCCESS;
+ err = Timer_Add_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "err=%d", err);
+ zassert_equal(
+ list_element.error_code, ERROR_CODE_SUCCESS, "%s",
+ bactext_error_code_name(list_element.error_code));
+ err = Timer_Remove_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "err=%d", err);
+ zassert_equal(
+ list_element.error_code, ERROR_CODE_SUCCESS, "%s",
+ bactext_error_code_name(list_element.error_code));
+ /* Add/RemoveListElement negative tests */
+ list_element.object_property = PROP_ALL;
+ err = Timer_Add_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_ERROR, "err=%d", err);
+ zassert_equal(
+ list_element.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, "%s",
+ bactext_error_code_name(list_element.error_code));
+ err = Timer_Remove_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_ERROR, "err=%d", err);
+ zassert_equal(
+ list_element.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, "%s",
+ bactext_error_code_name(list_element.error_code));
+ err = Timer_Add_List_Element(NULL);
+ zassert_equal(err, BACNET_STATUS_ABORT, "err=%d", err);
+ err = Timer_Remove_List_Element(NULL);
+ zassert_equal(err, BACNET_STATUS_ABORT, "err=%d", err);
+ /* default-timeout - out of range error */
+ wp_data.object_property = PROP_DEFAULT_TIMEOUT;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1000;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = UINT32_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* min-pres-value - out of range error */
+ wp_data.object_property = PROP_MIN_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = UINT32_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* max-pres-value - out of range error */
+ wp_data.object_property = PROP_MAX_PRES_VALUE;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = UINT32_MAX;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = UINT32_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* resolution - out of range error */
+ wp_data.object_property = PROP_RESOLUTION;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = UINT32_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* priority-for-writing - out of range error */
+ wp_data.object_property = PROP_PRIORITY_FOR_WRITING;
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = BACNET_MIN_PRIORITY;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ value.type.Unsigned_Int = BACNET_MAX_PRIORITY + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ value.type.Unsigned_Int = UINT8_MAX + 1ULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* state-change-values */
+ wp_data.object_property = PROP_STATE_CHANGE_VALUES;
+ wp_data.array_index = 1;
+ value.tag = BACNET_APPLICATION_TAG_TIMER_VALUE;
+ value.type.Timer_Value.tag = BACNET_APPLICATION_TAG_REAL;
+ value.type.Timer_Value.type.Real = 1.0f;
+ value.type.Timer_Value.next = NULL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_true(status, NULL);
+ wp_data.array_index = BACNET_ARRAY_ALL - 1;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_INVALID_ARRAY_INDEX, NULL);
+ /* write to all elements, but only include one element */
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, NULL);
+ /* state-change-values - wrong datatype */
+ wp_data.array_index = 1;
+ wp_data.application_data_len =
+ encode_context_real(wp_data.application_data, 42, 1.0f);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(
+ wp_data.error_code, ERROR_CODE_VALUE_OUT_OF_RANGE, "%s",
+ bactext_error_code_name(wp_data.error_code));
+ /* write to the size, but the size is read-only */
+ value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value.type.Unsigned_Int = 42;
+ wp_data.array_index = 0;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL);
+ /* read-only property */
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.priority = BACNET_MAX_PRIORITY;
+ wp_data.object_property = PROP_OBJECT_TYPE;
+ value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value.type.Enumerated = OBJECT_ANALOG_INPUT;
+ wp_data.application_data_len =
+ bacapp_encode_application_data(wp_data.application_data, &value);
+ status = Timer_Write_Property(&wp_data);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL);
+ zassert_false(status, NULL);
+ /* present-value API */
+ status = Timer_Present_Value_Set(instance, 0);
+ zassert_true(status, NULL);
+ status = Timer_Min_Pres_Value_Set(instance, 100);
+ zassert_true(status, NULL);
+ status = Timer_Present_Value_Set(instance, 1);
+ zassert_false(status, NULL);
+ status = Timer_Max_Pres_Value_Set(instance, 9999);
+ zassert_true(status, NULL);
+ status = Timer_Present_Value_Set(instance, 10000);
+ zassert_false(status, NULL);
+ status = Timer_Present_Value_Set(instance, Timer_Min_Pres_Value(instance));
+ zassert_true(status, NULL);
+ status = Timer_Present_Value_Set(instance, Timer_Max_Pres_Value(instance));
+ zassert_true(status, NULL);
+ /* negative testing of API */
+ test_member = Timer_Reference_List_Member_Element(instance + 1, 0);
+ zassert_true(test_member == NULL, NULL);
+ /* reliability and status flags */
+ status = Timer_Reliability_Set(instance, RELIABILITY_PROCESS_ERROR);
+ zassert_true(status, NULL);
+ /* context API */
+ Timer_Context_Set(instance, sample_context);
+ zassert_true(sample_context == Timer_Context_Get(instance), NULL);
+ zassert_true(NULL == Timer_Context_Get(instance + 1), NULL);
+ /* description API */
+ status = Timer_Description_Set(instance, sample_description);
+ zassert_true(status, NULL);
+ zassert_equal(sample_description, Timer_Description_ANSI(instance), NULL);
+ status = Timer_Description(instance, &cstring);
+ zassert_true(status, NULL);
+ status = characterstring_ansi_same(&cstring, sample_description);
+ zassert_true(status, NULL);
+ status = Timer_Description_Set(instance, NULL);
+ zassert_true(status, NULL);
+ status = characterstring_init_ansi(&cstring, "");
+ zassert_true(status, NULL);
+ status =
+ characterstring_ansi_same(&cstring, Timer_Description_ANSI(instance));
+ zassert_true(status, NULL);
+ /* cleanup */
+ status = Timer_Delete(instance);
+ zassert_true(status, NULL);
+ Timer_Cleanup();
+}
+
+static void test_Timer_Operation_Transition_Default(
+ const uint32_t instance,
+ BACNET_TIMER_STATE test_state,
+ BACNET_TIMER_TRANSITION test_transition)
+{
+ bool status = false;
+ BACNET_TIMER_STATE_CHANGE_VALUE *value = NULL;
+ BACNET_TIMER_STATE state = TIMER_STATE_IDLE;
+ BACNET_TIMER_TRANSITION transition = TIMER_TRANSITION_NONE;
+ uint32_t timeout = 0, test_timeout = 0;
+ BACNET_DATE_TIME bdatetime = { 0 }, test_bdatetime = { 0 };
+ BACNET_APPLICATION_DATA_VALUE test_value = { 0 };
+ int diff = 0, len = 0;
+
+ state = Timer_State(instance);
+ zassert_equal(state, test_state, NULL);
+ transition = Timer_Last_State_Change(instance);
+ zassert_equal(transition, test_transition, NULL);
+ if (Timer_Running(instance)) {
+ timeout = Timer_Default_Timeout(instance);
+ test_timeout = Timer_Initial_Timeout(instance);
+ zassert_equal(timeout, test_timeout, NULL);
+ status = Timer_Initial_Timeout_Set(instance, timeout);
+ zassert_true(status, NULL);
+ test_timeout = Timer_Initial_Timeout(instance);
+ zassert_equal(timeout, test_timeout, NULL);
+ test_timeout = Timer_Present_Value(instance);
+ zassert_equal(timeout, test_timeout, NULL);
+ }
+ datetime_local(&bdatetime.date, &bdatetime.time, NULL, NULL);
+ status = Timer_Update_Time(instance, &test_bdatetime);
+ zassert_true(status, NULL);
+ diff = datetime_compare(&bdatetime, &test_bdatetime);
+ zassert_equal(diff, 0, "diff=%d", diff);
+ status = Timer_Update_Time_Set(instance, &bdatetime);
+ zassert_true(status, NULL);
+ status = Timer_Update_Time(instance, &test_bdatetime);
+ zassert_true(status, NULL);
+ diff = datetime_compare(&bdatetime, &test_bdatetime);
+ zassert_equal(diff, 0, "diff=%d", diff);
+ status = Timer_Expiration_Time(instance, &test_bdatetime);
+ zassert_true(status, NULL);
+ diff = datetime_compare(&bdatetime, &test_bdatetime);
+ zassert_true(diff < 0, "diff=%d", diff);
+ zassert_equal(
+ Write_Property_Internal_Data.object_property, PROP_PRESENT_VALUE, NULL);
+ len = bacapp_decode_application_data(
+ Write_Property_Internal_Data.application_data,
+ Write_Property_Internal_Data.application_data_len, &test_value);
+ zassert_true(len > 0, "len=%d", len);
+ value = Timer_State_Change_Value(instance, test_transition);
+ zassert_equal(test_value.tag, value->tag, NULL);
+ zassert_equal(test_value.type.Enumerated, value->type.Enumerated, NULL);
+}
+
+/**
+ * @brief Test
+ */
+static void test_Timer_Operation(void)
+{
+ const uint32_t instance = 123;
+ uint32_t test_instance = 0;
+ bool status = false;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 };
+ BACNET_TIMER_STATE_CHANGE_VALUE *value = NULL;
+ BACNET_TIMER_STATE test_state = TIMER_STATE_IDLE;
+ BACNET_DATE_TIME bdatetime = { 0 };
+ uint32_t elapsed_time = 0;
+ unsigned members = 0, i = 0;
+
+ /* init */
+ Timer_Init();
+ Timer_Create(instance);
+ status = Timer_Valid_Instance(instance);
+ zassert_true(status, NULL);
+ /* set the local time */
+ datetime_set_values(&bdatetime, 2025, 10, 24, 10, 50, 42, 42);
+ datetime_timesync(&bdatetime.date, &bdatetime.time, false);
+ /* configure the reference members and the write property values */
+ Timer_Write_Property_Internal_Callback_Set(Write_Property_Internal);
+ members = Timer_Reference_List_Member_Capacity(instance);
+ for (i = 0; i < members; i++) {
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_BINARY_VALUE;
+ member.objectIdentifier.instance = 1 + i;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Set(instance, i, &member);
+ }
+ /* check the transitions boundaries */
+ value = Timer_State_Change_Value(instance, TIMER_TRANSITION_NONE);
+ zassert_true(value == NULL, NULL);
+ value = Timer_State_Change_Value(instance, TIMER_TRANSITION_MAX);
+ zassert_true(value == NULL, NULL);
+ /* configure the transitions */
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_IDLE_TO_RUNNING);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_ACTIVE;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_RUNNING_TO_IDLE);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_INACTIVE;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_EXPIRED_TO_IDLE);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_INACTIVE;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_RUNNING_TO_EXPIRED);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_INACTIVE;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_FORCED_TO_EXPIRED);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_INACTIVE;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_EXPIRED_TO_RUNNING);
+ value->tag = BACNET_APPLICATION_TAG_ENUMERATED;
+ value->type.Enumerated = BINARY_ACTIVE;
+ /* alternate API */
+ status = Timer_State_Change_Value_Set(
+ instance, TIMER_TRANSITION_EXPIRED_TO_RUNNING, value);
+ zassert_true(status, NULL);
+ /* start timer using a write to timer-running property to use defaults */
+ /* IDLE_TO_RUNNING */
+ status = Timer_State_Set(instance, TIMER_STATE_IDLE);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_IDLE, NULL);
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ status = Timer_Running(instance);
+ zassert_true(status, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_RUNNING, TIMER_TRANSITION_IDLE_TO_RUNNING);
+ /* RUNNING_TO_RUNNING */
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ status = Timer_Running(instance);
+ zassert_true(status, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_RUNNING, TIMER_TRANSITION_RUNNING_TO_RUNNING);
+ /* EXPIRED_TO_RUNNING */
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ elapsed_time = Timer_Present_Value(instance);
+ Timer_Task(instance, elapsed_time - 1);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ Timer_Task(instance, elapsed_time);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_EXPIRED, NULL);
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ status = Timer_Running(instance);
+ zassert_true(status, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_RUNNING, TIMER_TRANSITION_EXPIRED_TO_RUNNING);
+ /* EXPIRED_TO_IDLE */
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ elapsed_time = Timer_Present_Value(instance);
+ Timer_Task(instance, elapsed_time);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_EXPIRED, NULL);
+ status = Timer_State_Set(instance, TIMER_STATE_IDLE);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_IDLE, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_IDLE, TIMER_TRANSITION_EXPIRED_TO_IDLE);
+ /* start timer using a write to timer-state property to use defaults */
+ /* RUNNING_TO_IDLE */
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ status = Timer_Running(instance);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ status = Timer_State_Set(instance, TIMER_STATE_IDLE);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_IDLE, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_IDLE, TIMER_TRANSITION_RUNNING_TO_IDLE);
+ Timer_Task(instance, elapsed_time);
+ /* RUNNING_TO_EXPIRED */
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_RUNNING, NULL);
+ status = Timer_Running_Set(instance, false);
+ zassert_true(status, NULL);
+ test_state = Timer_State(instance);
+ zassert_equal(test_state, TIMER_STATE_EXPIRED, NULL);
+ test_Timer_Operation_Transition_Default(
+ instance, TIMER_STATE_EXPIRED, TIMER_TRANSITION_FORCED_TO_EXPIRED);
+ Timer_Task(instance, elapsed_time);
+ /* cleanup instance */
+ status = Timer_Delete(instance);
+ zassert_true(status, NULL);
+ /* test create of next instance */
+ test_instance = Timer_Create(BACNET_MAX_INSTANCE);
+ zassert_not_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ test_instance = Timer_Create(test_instance);
+ zassert_not_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ test_instance = Timer_Create(BACNET_MAX_INSTANCE + 1);
+ zassert_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
+ /* cleanup all */
+ Timer_Cleanup();
+}
+/**
+ * @}
+ */
+
+void test_main(void)
+{
+ ztest_test_suite(
+ timer_tests, ztest_unit_test(test_Timer_Read_Write),
+ ztest_unit_test(test_Timer_Operation));
+
+ ztest_run_test_suite(timer_tests);
+}
diff --git a/test/bacnet/basic/object/trendlog/CMakeLists.txt b/test/bacnet/basic/object/trendlog/CMakeLists.txt
index 6505d4cd..7923c1aa 100644
--- a/test/bacnet/basic/object/trendlog/CMakeLists.txt
+++ b/test/bacnet/basic/object/trendlog/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
index 672cf18d..75cc76d6 100644
--- a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
+++ b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
@@ -83,6 +83,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/schedule.c
${SRC_DIR}/bacnet/basic/object/structured_view.c
${SRC_DIR}/bacnet/basic/object/time_value.c
+ ${SRC_DIR}/bacnet/basic/object/timer.c
${SRC_DIR}/bacnet/basic/object/trendlog.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
@@ -103,6 +104,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/property.c
${SRC_DIR}/bacnet/reject.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/wp.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/cov/CMakeLists.txt b/test/bacnet/cov/CMakeLists.txt
index 8fb77c88..25aaabba 100644
--- a/test/bacnet/cov/CMakeLists.txt
+++ b/test/bacnet/cov/CMakeLists.txt
@@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt
index 13dc80a8..b890c20a 100644
--- a/test/bacnet/create_object/CMakeLists.txt
+++ b/test/bacnet/create_object/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/datalink/bsc-datalink/CMakeLists.txt b/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
index 3c90014d..e28013f3 100644
--- a/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
@@ -195,6 +195,7 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/datalink/bsc-node/CMakeLists.txt b/test/bacnet/datalink/bsc-node/CMakeLists.txt
index 6494ded0..568aefe0 100644
--- a/test/bacnet/datalink/bsc-node/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-node/CMakeLists.txt
@@ -185,6 +185,7 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/datalink/bsc-socket/CMakeLists.txt b/test/bacnet/datalink/bsc-socket/CMakeLists.txt
index 6a373fda..7363f5f7 100644
--- a/test/bacnet/datalink/bsc-socket/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-socket/CMakeLists.txt
@@ -183,6 +183,7 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/datalink/hub-sc/CMakeLists.txt b/test/bacnet/datalink/hub-sc/CMakeLists.txt
index 2f763e0a..ac8e48a7 100644
--- a/test/bacnet/datalink/hub-sc/CMakeLists.txt
+++ b/test/bacnet/datalink/hub-sc/CMakeLists.txt
@@ -188,6 +188,7 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/wp.c
diff --git a/test/bacnet/datetime/src/main.c b/test/bacnet/datetime/src/main.c
index 6e100282..6f2eb11c 100644
--- a/test/bacnet/datetime/src/main.c
+++ b/test/bacnet/datetime/src/main.c
@@ -100,6 +100,24 @@ static void testBACnetDateTimeAdd(void)
datetime_set_values(&test_bdatetime, 2013, 6, 5, 23, 59, 59, 0);
diff = datetime_compare(&test_bdatetime, &bdatetime);
zassert_equal(diff, 0, NULL);
+
+ datetime_set_values(&bdatetime, 2013, 6, 6, 0, 59, 59, 0);
+ datetime_add_seconds(&bdatetime, 1);
+ datetime_set_values(&test_bdatetime, 2013, 6, 6, 1, 0, 0, 0);
+ diff = datetime_compare(&test_bdatetime, &bdatetime);
+ zassert_equal(diff, 0, "diff=%d", diff);
+
+ datetime_set_values(&bdatetime, 2013, 6, 6, 0, 0, 0, 0);
+ datetime_add_seconds(&bdatetime, -1);
+ datetime_set_values(&test_bdatetime, 2013, 6, 5, 23, 59, 59, 0);
+ diff = datetime_compare(&test_bdatetime, &bdatetime);
+ zassert_equal(diff, 0, "diff=%d", diff);
+
+ datetime_set_values(&bdatetime, 2013, 6, 6, 0, 59, 59, 99);
+ datetime_add_milliseconds(&bdatetime, 10);
+ datetime_set_values(&test_bdatetime, 2013, 6, 6, 1, 0, 0, 0);
+ diff = datetime_compare(&test_bdatetime, &bdatetime);
+ zassert_equal(diff, 0, "diff=%d", diff);
}
static void testBACnetDateTimeSeconds(void)
diff --git a/test/bacnet/delete_object/CMakeLists.txt b/test/bacnet/delete_object/CMakeLists.txt
index 0233cac1..96868948 100644
--- a/test/bacnet/delete_object/CMakeLists.txt
+++ b/test/bacnet/delete_object/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/event/CMakeLists.txt b/test/bacnet/event/CMakeLists.txt
index ba49b2c5..3494846d 100644
--- a/test/bacnet/event/CMakeLists.txt
+++ b/test/bacnet/event/CMakeLists.txt
@@ -48,7 +48,6 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/datetime.c
${SRC_DIR}/bacnet/basic/sys/days.c
- ${SRC_DIR}/bacnet/timestamp.c
# Dependencies of bacapp.c
${SRC_DIR}/bacnet/access_rule.c
${SRC_DIR}/bacnet/bacaction.c
@@ -58,6 +57,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bactimevalue.c
${SRC_DIR}/bacnet/dailyschedule.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/calendar_entry.c
diff --git a/test/bacnet/getalarm/CMakeLists.txt b/test/bacnet/getalarm/CMakeLists.txt
index 94ae8e73..eea6560c 100644
--- a/test/bacnet/getalarm/CMakeLists.txt
+++ b/test/bacnet/getalarm/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/getevent/CMakeLists.txt b/test/bacnet/getevent/CMakeLists.txt
index 0494218f..2b26be9c 100644
--- a/test/bacnet/getevent/CMakeLists.txt
+++ b/test/bacnet/getevent/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/hostnport/CMakeLists.txt b/test/bacnet/hostnport/CMakeLists.txt
index ab73c882..05c67ee7 100644
--- a/test/bacnet/hostnport/CMakeLists.txt
+++ b/test/bacnet/hostnport/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/list_element/CMakeLists.txt b/test/bacnet/list_element/CMakeLists.txt
index ad4cd459..f3a1342e 100644
--- a/test/bacnet/list_element/CMakeLists.txt
+++ b/test/bacnet/list_element/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/lso/CMakeLists.txt b/test/bacnet/lso/CMakeLists.txt
index 3526b51c..ba900945 100644
--- a/test/bacnet/lso/CMakeLists.txt
+++ b/test/bacnet/lso/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/ptransfer/CMakeLists.txt b/test/bacnet/ptransfer/CMakeLists.txt
index cda3351a..dc591bd4 100644
--- a/test/bacnet/ptransfer/CMakeLists.txt
+++ b/test/bacnet/ptransfer/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/rpm/CMakeLists.txt b/test/bacnet/rpm/CMakeLists.txt
index 25155dc9..bf81c833 100644
--- a/test/bacnet/rpm/CMakeLists.txt
+++ b/test/bacnet/rpm/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/memcopy.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/secure_connect/CMakeLists.txt b/test/bacnet/secure_connect/CMakeLists.txt
index 6441a7f9..b9af1d0f 100644
--- a/test/bacnet/secure_connect/CMakeLists.txt
+++ b/test/bacnet/secure_connect/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
diff --git a/test/bacnet/specialevent/CMakeLists.txt b/test/bacnet/specialevent/CMakeLists.txt
index 82cba2aa..6365a499 100644
--- a/test/bacnet/specialevent/CMakeLists.txt
+++ b/test/bacnet/specialevent/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/bactimevalue.c
${SRC_DIR}/bacnet/dailyschedule.c
diff --git a/test/bacnet/timer_value/CMakeLists.txt b/test/bacnet/timer_value/CMakeLists.txt
new file mode 100644
index 00000000..7d5fdee6
--- /dev/null
+++ b/test/bacnet/timer_value/CMakeLists.txt
@@ -0,0 +1,54 @@
+# 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
+ BACNET_TIMER_VALUE_ALL
+ BACAPP_ALL
+ )
+
+include_directories(
+ ${SRC_DIR}
+ ${TST_DIR}/ztest/include
+ )
+
+add_executable(${PROJECT_NAME}
+ # File(s) under test
+ ${SRC_DIR}/bacnet/timer_value.c
+ # Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/bacdcode.c
+ ${SRC_DIR}/bacnet/bacint.c
+ ${SRC_DIR}/bacnet/bacreal.c
+ ${SRC_DIR}/bacnet/bacstr.c
+ ${SRC_DIR}/bacnet/bactext.c
+ ${SRC_DIR}/bacnet/basic/sys/bigend.c
+ ${SRC_DIR}/bacnet/basic/sys/days.c
+ ${SRC_DIR}/bacnet/datetime.c
+ ${SRC_DIR}/bacnet/hostnport.c
+ ${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/lighting.c
+ # Test and test library files
+ ./src/main.c
+ ${ZTST_DIR}/ztest_mock.c
+ ${ZTST_DIR}/ztest.c
+ )
diff --git a/test/bacnet/timer_value/src/main.c b/test/bacnet/timer_value/src/main.c
new file mode 100644
index 00000000..e5b0a06f
--- /dev/null
+++ b/test/bacnet/timer_value/src/main.c
@@ -0,0 +1,253 @@
+/**
+ * @file
+ * @brief Unit test for BACnetChannelValue.
+ * @author Steve Karg
+ * @date October 2024
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "bacnet/bactext.h"
+#include "bacnet/bacstr.h"
+#include "bacnet/timer_value.h"
+
+/**
+ * @addtogroup bacnet_tests
+ * @{
+ */
+
+/** @brief try decoding a real sample from Siemens, captured with wireshark */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(BACnetTimerValue_Tests, test_BACnetTimerValue)
+#else
+static void test_BACnetTimerValue(void)
+#endif
+{
+ uint8_t apdu[MAX_APDU];
+ int apdu_len, test_len, null_len;
+ bool status;
+ BACNET_TIMER_STATE_CHANGE_VALUE *value;
+ BACNET_TIMER_STATE_CHANGE_VALUE case_value[] = {
+ { .tag = BACNET_APPLICATION_TAG_NULL, .type.Real = 0.0f, .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_NO_VALUE,
+ .type.Unsigned_Int = 0,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_BOOLEAN,
+ .type.Boolean = false,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ .type.Unsigned_Int = 0xDEADBEEF,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_SIGNED_INT,
+ .type.Signed_Int = 0x00C0FFEE,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_REAL,
+ .type.Real = 3.141592654f,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_DOUBLE,
+ .type.Double = 2.32323232323,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_ENUMERATED,
+ .type.Enumerated = 0x0BADF00D,
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_OCTET_STRING,
+ .type.Octet_String = { .length = 4,
+ .value = { 0xDE, 0xAD, 0xBE, 0xEF } },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_CHARACTER_STRING,
+ .type.Character_String = { .encoding = CHARACTER_UTF8,
+ .length = 11,
+ .value = "Hello World" },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_BIT_STRING,
+ .type.Bit_String = { .bits_used = 10, .value = { 0xFF, 0x03 } },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_DATE,
+ .type.Date = { 2024, 12, 31, 4 },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_TIME,
+ .type.Time = { 23, 59, 58, 99 },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_OBJECT_ID,
+ .type.Object_Id = { OBJECT_ANALOG_INPUT, 12345 },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_DATETIME,
+ .type.Date_Time = { .date = { 2024, 12, 31, 4 },
+ .time = { 23, 59, 58, 99 } },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX,
+ .type.Constructed_Value = { .data = { 1, 2, 3, 4 }, .data_len = 4 },
+ .next = NULL },
+ { .tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND,
+ .type.Lighting_Command.operation = BACNET_LIGHTS_NONE,
+ .next = NULL },
+ };
+ struct to_ascii_test_value {
+ const char *string;
+ BACNET_TIMER_STATE_CHANGE_VALUE value;
+ } to_ascii_values[] = {
+ { "NULL",
+ { .tag = BACNET_APPLICATION_TAG_NULL,
+ .type.Real = 0.0f,
+ .next = NULL } },
+ { "FALSE",
+ { .tag = BACNET_APPLICATION_TAG_BOOLEAN,
+ .type.Boolean = false,
+ .next = NULL } },
+ { "TRUE",
+ { .tag = BACNET_APPLICATION_TAG_BOOLEAN,
+ .type.Boolean = true,
+ .next = NULL } },
+ { "1234567890",
+ { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT,
+ .type.Unsigned_Int = 1234567890,
+ .next = NULL } },
+ { "-1234567890",
+ { .tag = BACNET_APPLICATION_TAG_SIGNED_INT,
+ .type.Signed_Int = -1234567890,
+ .next = NULL } },
+ { "3.141593",
+ { .tag = BACNET_APPLICATION_TAG_REAL,
+ .type.Real = 3.141592654f,
+ .next = NULL } },
+ { "-3.141593",
+ { .tag = BACNET_APPLICATION_TAG_REAL,
+ .type.Real = -3.141592654f,
+ .next = NULL } },
+ { "-3.141593",
+ { .tag = BACNET_APPLICATION_TAG_DOUBLE,
+ .type.Double = -3.141592654,
+ .next = NULL } },
+ { "0",
+ { .tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND,
+ .type.Lighting_Command = { .operation = BACNET_LIGHTS_NONE },
+ .next = NULL } },
+ { "1,75.000000,5,8",
+ { .tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND,
+ .type.Lighting_Command = { .operation = BACNET_LIGHTS_FADE_TO,
+ .use_fade_time = true,
+ .fade_time = 5,
+ .use_target_level = true,
+ .target_level = 75.0f,
+ .use_priority = true,
+ .priority = 8 },
+ .next = NULL } },
+ };
+ struct from_ascii_test_value {
+ const char *string;
+ BACNET_APPLICATION_TAG tag;
+ } from_ascii_values[] = {
+ { "NULL", BACNET_APPLICATION_TAG_NULL },
+ { "FALSE", BACNET_APPLICATION_TAG_BOOLEAN },
+ { "1234567890", BACNET_APPLICATION_TAG_UNSIGNED_INT },
+ { "-1234567890", BACNET_APPLICATION_TAG_SIGNED_INT },
+ { "3.141592654", BACNET_APPLICATION_TAG_REAL },
+ { "-3.141592654", BACNET_APPLICATION_TAG_REAL },
+ { "F1.21", BACNET_APPLICATION_TAG_REAL },
+ { "f1.21", BACNET_APPLICATION_TAG_REAL },
+ { "D1.21", BACNET_APPLICATION_TAG_DOUBLE },
+ { "d1.21", BACNET_APPLICATION_TAG_DOUBLE },
+ { "L0", BACNET_APPLICATION_TAG_LIGHTING_COMMAND },
+ { "l0", BACNET_APPLICATION_TAG_LIGHTING_COMMAND },
+ };
+ size_t i;
+ BACNET_TIMER_STATE_CHANGE_VALUE test_value = { 0 };
+ char test_string[64];
+
+ bacnet_timer_value_link_array(case_value, ARRAY_SIZE(case_value));
+ value = &case_value[0];
+ while (value) {
+ null_len = bacnet_timer_value_encode(NULL, sizeof(apdu), value);
+ if (value->tag != BACNET_APPLICATION_TAG_NULL) {
+ zassert_not_equal(null_len, 0, NULL);
+ }
+ apdu_len = bacnet_timer_value_encode(apdu, sizeof(apdu), value);
+ zassert_equal(
+ apdu_len, null_len, "value->tag: %s len=%d null_len=%d",
+ bactext_application_tag_name(value->tag), apdu_len, null_len);
+ null_len = bacnet_timer_value_decode(NULL, apdu_len, &test_value);
+ zassert_equal(null_len, BACNET_STATUS_ERROR, NULL);
+ zassert_equal(
+ null_len, BACNET_STATUS_ERROR, "value->tag: %s null_len=%d",
+ bactext_application_tag_name(value->tag), null_len);
+ null_len = bacnet_timer_value_decode(apdu, apdu_len, NULL);
+ zassert_equal(
+ null_len, BACNET_STATUS_ERROR, "value->tag: %s null_len=%d",
+ bactext_application_tag_name(value->tag), null_len);
+ test_len = bacnet_timer_value_decode(apdu, apdu_len, &test_value);
+ zassert_not_equal(
+ test_len, BACNET_STATUS_ERROR, "value->tag: %s test_len=%d",
+ bactext_application_tag_name(value->tag), test_len);
+ zassert_equal(
+ test_len, apdu_len, "value->tag: %s test_len=%d apdu_len=%d",
+ bactext_application_tag_name(value->tag), test_len, apdu_len);
+ zassert_equal(
+ value->tag, test_value.tag, "value->tag: %s test_tag=%s",
+ bactext_application_tag_name(value->tag),
+ bactext_application_tag_name(test_value.tag));
+ status = bacnet_timer_value_same(value, &test_value);
+ zassert_true(
+ status, "decode: different: %s",
+ bactext_application_tag_name(value->tag));
+ status = bacnet_timer_value_copy(&test_value, value);
+ zassert_true(
+ status, "copy: failed: %s",
+ bactext_application_tag_name(value->tag));
+ status = bacnet_timer_value_same(value, &test_value);
+ zassert_true(
+ status, "copy: different: %s",
+ bactext_application_tag_name(value->tag));
+ value = value->next;
+ }
+ for (i = 0; i < ARRAY_SIZE(from_ascii_values); i++) {
+ status = bacnet_timer_value_from_ascii(
+ &test_value, from_ascii_values[i].string);
+ zassert_true(
+ status, "from_ascii: failed: %s", from_ascii_values[i].string);
+ zassert_equal(
+ test_value.tag, from_ascii_values[i].tag, "from_ascii: %s",
+ from_ascii_values[i].string);
+ }
+ for (i = 0; i < ARRAY_SIZE(to_ascii_values); i++) {
+ status = bacnet_timer_value_to_ascii(
+ &to_ascii_values[i].value, test_string, sizeof(test_string));
+ zassert_true(status, "to_ascii: failed: %s", to_ascii_values[i].string);
+ if (status) {
+ zassert_true(
+ bacnet_stricmp(test_string, to_ascii_values[i].string) == 0,
+ "to_ascii: failed: got %s expected: %s", test_string,
+ to_ascii_values[i].string);
+ }
+ }
+ /* no-value API */
+ null_len = bacnet_timer_value_no_value_encode(NULL);
+ zassert_not_equal(null_len, 0, NULL);
+ apdu_len = bacnet_timer_value_no_value_encode(apdu);
+ zassert_not_equal(apdu_len, 0, NULL);
+ zassert_equal(apdu_len, null_len, NULL);
+ test_len = bacnet_timer_value_no_value_decode(NULL, 0);
+ zassert_equal(test_len, BACNET_STATUS_ERROR, NULL);
+ test_len = bacnet_timer_value_no_value_decode(apdu, apdu_len);
+ zassert_equal(apdu_len, test_len, NULL);
+}
+
+/**
+ * @}
+ */
+
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST_SUITE(BACnetTimerValue_Tests, NULL, NULL, NULL, NULL, NULL);
+#else
+void test_main(void)
+{
+ ztest_test_suite(
+ BACnetTimerValue_Tests, ztest_unit_test(test_BACnetTimerValue));
+
+ ztest_run_test_suite(BACnetTimerValue_Tests);
+}
+#endif
diff --git a/test/bacnet/timesync/CMakeLists.txt b/test/bacnet/timesync/CMakeLists.txt
index 479a1bb5..a2452cf6 100644
--- a/test/bacnet/timesync/CMakeLists.txt
+++ b/test/bacnet/timesync/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/weeklyschedule.c
${SRC_DIR}/bacnet/bactimevalue.c
diff --git a/test/bacnet/weeklyschedule/CMakeLists.txt b/test/bacnet/weeklyschedule/CMakeLists.txt
index 10b53992..dfedeb81 100644
--- a/test/bacnet/weeklyschedule/CMakeLists.txt
+++ b/test/bacnet/weeklyschedule/CMakeLists.txt
@@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
diff --git a/test/bacnet/wp/CMakeLists.txt b/test/bacnet/wp/CMakeLists.txt
index d8058007..f811ed87 100644
--- a/test/bacnet/wp/CMakeLists.txt
+++ b/test/bacnet/wp/CMakeLists.txt
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/wpm/CMakeLists.txt b/test/bacnet/wpm/CMakeLists.txt
index cf48c974..1708c908 100644
--- a/test/bacnet/wpm/CMakeLists.txt
+++ b/test/bacnet/wpm/CMakeLists.txt
@@ -54,6 +54,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/weeklyschedule.c
diff --git a/test/bacnet/write_group/CMakeLists.txt b/test/bacnet/write_group/CMakeLists.txt
index 993c59e8..2698fd5a 100644
--- a/test/bacnet/write_group/CMakeLists.txt
+++ b/test/bacnet/write_group/CMakeLists.txt
@@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/weeklyschedule.c
|