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