From b357bca5dd06a43716952c8b4a3c23373cc64c2e Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Wed, 24 Sep 2025 07:09:56 -0500 Subject: [PATCH] Converted device object test to use common read-write property test. Extended the basic BACnet device object example API. (#1106) * Converted device object test to use common read-write property test. Extended the basic BACnet device object example API. * Created BACnet/IP and COV test mocks to enable device object testing with less dependencies. --- src/bacnet/basic/object/device.c | 87 +- src/bacnet/basic/server/bacnet_device.c | 1187 ++++++++++++----- test/CMakeLists.txt | 2 + .../bacnet/basic/object/device/CMakeLists.txt | 11 +- test/bacnet/basic/object/test/apdu_mock.c | 28 + test/bacnet/basic/object/test/bip_mock.c | 28 + test/bacnet/basic/object/test/cov_mock.c | 17 + test/bacnet/basic/object/test/property_test.c | 4 +- .../basic/server/bacnet_device/CMakeLists.txt | 124 ++ .../basic/server/bacnet_device/src/main.c | 443 ++++++ 10 files changed, 1570 insertions(+), 361 deletions(-) create mode 100644 test/bacnet/basic/object/test/bip_mock.c create mode 100644 test/bacnet/basic/object/test/cov_mock.c create mode 100644 test/bacnet/basic/server/bacnet_device/CMakeLists.txt create mode 100644 test/bacnet/basic/server/bacnet_device/src/main.c diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 3539410c..427dd9dc 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -23,7 +23,6 @@ #if defined(BACDL_MSTP) #include "bacnet/datalink/dlmstp.h" #endif -#include "bacnet/basic/object/device.h" /* me */ #include "bacnet/basic/services.h" #include "bacnet/basic/binding/address.h" /* include the device object */ @@ -492,39 +491,69 @@ void Device_Objects_Property_List( return; } -/* clang-format off */ /* These three arrays are used by the ReadPropertyMultiple handler */ static const int Device_Properties_Required[] = { - PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, - PROP_SYSTEM_STATUS, PROP_VENDOR_NAME, PROP_VENDOR_IDENTIFIER, - PROP_MODEL_NAME, PROP_FIRMWARE_REVISION, PROP_APPLICATION_SOFTWARE_VERSION, - PROP_PROTOCOL_VERSION, PROP_PROTOCOL_REVISION, - PROP_PROTOCOL_SERVICES_SUPPORTED, PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, - PROP_OBJECT_LIST, PROP_MAX_APDU_LENGTH_ACCEPTED, - PROP_SEGMENTATION_SUPPORTED, PROP_APDU_TIMEOUT, - PROP_NUMBER_OF_APDU_RETRIES, PROP_DEVICE_ADDRESS_BINDING, - PROP_DATABASE_REVISION, -1 + /* List of Required properties in this object */ + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_REVISION, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_OBJECT_LIST, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, + -1 }; static const int Device_Properties_Optional[] = { +/* List of Optional properties in this object */ #if defined(BACDL_MSTP) - PROP_MAX_MASTER, PROP_MAX_INFO_FRAMES, + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, #endif - PROP_DESCRIPTION, PROP_LOCAL_TIME, PROP_UTC_OFFSET, PROP_LOCAL_DATE, - PROP_DAYLIGHT_SAVINGS_STATUS, PROP_LOCATION, PROP_ACTIVE_COV_SUBSCRIPTIONS, - PROP_SERIAL_NUMBER, PROP_TIME_OF_DEVICE_RESTART, + PROP_DESCRIPTION, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_LOCAL_DATE, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_LOCATION, + PROP_ACTIVE_COV_SUBSCRIPTIONS, + PROP_SERIAL_NUMBER, + PROP_TIME_OF_DEVICE_RESTART, #if defined(BACNET_TIME_MASTER) - PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, - PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_TIME_SYNCHRONIZATION_INTERVAL, + PROP_ALIGN_INTERVALS, + PROP_INTERVAL_OFFSET, #endif -1 }; -static const int Device_Properties_Proprietary[] = { - -1 -}; -/* clang-format on */ +static const int Device_Properties_Proprietary[] = { -1 }; +/** + * @brief Returns the list of required, optional, and proprietary properties + * for the Device object. + * @param pRequired [out] Pointer to the list of required properties + * @param pOptional [out] Pointer to the list of optional properties + * @param pProprietary [out] Pointer to the list of proprietary properties + * @note The lists are terminated with -1. + * @note The lists are not allocated, so do not free them. + * @note The lists are static, so do not modify them. + * @ingroup ObjIntf + */ void Device_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { @@ -839,6 +868,11 @@ bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name) return status; } +/** + * @brief Initialize the Device Object Name with an ANSI C string + * @param value [in] The object name as a null-terminated string + * @return True on success, else False + */ bool Device_Object_Name_ANSI_Init(const char *value) { return characterstring_init_ansi(&My_Object_Name, value); @@ -1092,11 +1126,19 @@ BACNET_SEGMENTATION Device_Segmentation_Supported(void) return SEGMENTATION_NONE; } +/** + * @brief Get the Database Revision property of the Device Object + * @return The Database Revision property of the Device Object + */ uint32_t Device_Database_Revision(void) { return Database_Revision; } +/** + * @brief Set the Database Revision property of the Device Object + * @param revision [in] The new value for the Database Revision property + */ void Device_Set_Database_Revision(uint32_t revision) { Database_Revision = revision; @@ -1950,6 +1992,7 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; } + break; } return status; @@ -2061,7 +2104,7 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) if (wp_data->object_property == PROP_PROPERTY_LIST) { wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - return status; + return false; } #endif if (wp_data->object_property == PROP_OBJECT_NAME) { diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c index b25f8d78..369ebd88 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -1,26 +1,33 @@ /** * @file * @brief Base "class" for handling all BACnet objects belonging - * to a BACnet device, as well as Device-specific properties. + * to a BACnet device, as well as Device-specific properties. * @author Steve Karg * @date March 2024 - * @copyright SPDX-License-Identifier: Apache-2.0 + * @copyright SPDX-License-Identifier: MIT */ #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/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/datetime.h" #include "bacnet/apdu.h" -#include "bacnet/dcc.h" -#include "bacnet/datalink/datalink.h" -#include "bacnet/property.h" +#include "bacnet/wp.h" /* WriteProperty handling */ +#include "bacnet/rp.h" /* ReadProperty handling */ +#include "bacnet/dcc.h" /* DeviceCommunicationControl handling */ #include "bacnet/version.h" +#if defined(BACDL_MSTP) +#include "bacnet/datalink/dlmstp.h" +#endif #include "bacnet/basic/services.h" -/* objects */ +#include "bacnet/basic/binding/address.h" +/* include the device object */ +#include "bacnet/basic/object/device.h" #include "bacnet/basic/object/acc.h" #include "bacnet/basic/object/ai.h" #include "bacnet/basic/object/ao.h" @@ -30,6 +37,7 @@ #include "bacnet/basic/object/bv.h" #include "bacnet/basic/object/calendar.h" #include "bacnet/basic/object/command.h" +#include "bacnet/basic/object/program.h" #include "bacnet/basic/object/lc.h" #include "bacnet/basic/object/lsp.h" #include "bacnet/basic/object/lsz.h" @@ -52,7 +60,7 @@ #include "bacnet/basic/object/netport.h" #include "bacnet/basic/object/color_object.h" #include "bacnet/basic/object/color_temperature.h" -#include "bacnet/basic/object/device.h" +#include "bacnet/basic/object/program.h" #ifdef CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION #define BACNET_DEVICE_VERSION CONFIG_BACNET_BASIC_DEVICE_OBJECT_VERSION @@ -190,7 +198,9 @@ #endif #endif -static object_functions_t Object_Table[] = { +/* may be overridden by outside table */ +static object_functions_t *Object_Table; +static object_functions_t My_Object_Table[] = { { OBJECT_DEVICE, NULL, /* don't init - recursive! */ Device_Count, @@ -786,74 +796,6 @@ static object_functions_t Object_Table[] = { } }; -/* local data */ -static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE; -static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; -static BACNET_CHARACTER_STRING My_Object_Name; -static const char *Device_Name_Default = BACNET_DEVICE_OBJECT_NAME; -static const char *Device_Description_Default = BACNET_DEVICE_DESCRIPTION; -static const char *Device_Vendor_Name_Default = BACNET_VENDOR_NAME; -static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; -static const char *Model_Name = BACNET_DEVICE_MODEL_NAME; -static const char *Device_Location_Default = BACNET_DEVICE_LOCATION_NAME; -static const char *Application_Software_Version = BACNET_DEVICE_VERSION; -static const char *Firmware_Revision = BACNET_VERSION_TEXT; -static uint32_t Database_Revision; -static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; -static BACNET_CHARACTER_STRING Reinit_Password; -static write_property_function Device_Write_Property_Store_Callback; -static uint8_t Device_UUID[16]; -static const char *Serial_Number = BACNET_DEVICE_SERIAL_NUMBER; -static BACNET_TIMESTAMP Time_Of_Device_Restart; - -/* These three arrays are used by the ReadPropertyMultiple handler */ -static const int Device_Properties_Required[] = { - /* List of Required properties in this object */ - PROP_OBJECT_IDENTIFIER, - PROP_OBJECT_NAME, - PROP_OBJECT_TYPE, - PROP_SYSTEM_STATUS, - PROP_VENDOR_NAME, - PROP_VENDOR_IDENTIFIER, - PROP_MODEL_NAME, - PROP_FIRMWARE_REVISION, - PROP_APPLICATION_SOFTWARE_VERSION, - PROP_PROTOCOL_VERSION, - PROP_PROTOCOL_REVISION, - PROP_PROTOCOL_SERVICES_SUPPORTED, - PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, - PROP_OBJECT_LIST, - PROP_MAX_APDU_LENGTH_ACCEPTED, - PROP_SEGMENTATION_SUPPORTED, - PROP_APDU_TIMEOUT, - PROP_NUMBER_OF_APDU_RETRIES, - PROP_DEVICE_ADDRESS_BINDING, - PROP_DATABASE_REVISION, - -1 -}; - -static const int Device_Properties_Optional[] = { - /* List of Optional properties in this object */ - PROP_DESCRIPTION, - PROP_LOCATION, -#if defined(BACDL_MSTP) - PROP_MAX_MASTER, - PROP_MAX_INFO_FRAMES, -#endif -#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0) - PROP_ACTIVE_COV_SUBSCRIPTIONS, -#endif - PROP_LOCAL_DATE, - PROP_LOCAL_TIME, - PROP_UTC_OFFSET, - PROP_DAYLIGHT_SAVINGS_STATUS, - PROP_SERIAL_NUMBER, - PROP_TIME_OF_DEVICE_RESTART, - -1 -}; - -static const int Device_Properties_Proprietary[] = { -1 }; - /** Glue function to let the Device object, when called by a handler, * lookup which Object type needs to be invoked. * @ingroup ObjHelpers @@ -867,19 +809,37 @@ Device_Objects_Find_Functions(BACNET_OBJECT_TYPE Object_Type) { struct object_functions *pObject = NULL; - pObject = &Object_Table[0]; + pObject = Object_Table; while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { /* handle each object type */ if (pObject->Object_Type == Object_Type) { return (pObject); } - pObject++; } return (NULL); } +/** Try to find a rr_info_function helper function for the requested object + * type. + * @ingroup ObjIntf + * + * @param object_type [in] The type of BACnet Object the handler wants to + * access. + * @return Pointer to the object helper function that implements the + * ReadRangeInfo function, Object_RR_Info, for this type of Object on + * success, else a NULL pointer if the type of Object isn't supported + * or doesn't have a ReadRangeInfo function. + */ +rr_info_function Device_Objects_RR_Info(BACNET_OBJECT_TYPE object_type) +{ + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(object_type); + return (pObject != NULL ? pObject->Object_RR_Info : NULL); +} + /** For a given object type, returns the special property list. * This function is used for ReadPropertyMultiple calls which want * just Required, just Optional, or All properties. @@ -931,6 +891,74 @@ void Device_Objects_Property_List( return; } +/* These three arrays are used by the ReadPropertyMultiple handler */ +static const int Device_Properties_Required[] = { + /* List of Required properties in this object */ + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_SYSTEM_STATUS, + PROP_VENDOR_NAME, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, + PROP_FIRMWARE_REVISION, + PROP_APPLICATION_SOFTWARE_VERSION, + PROP_PROTOCOL_VERSION, + PROP_PROTOCOL_REVISION, + PROP_PROTOCOL_SERVICES_SUPPORTED, + PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, + PROP_OBJECT_LIST, + PROP_MAX_APDU_LENGTH_ACCEPTED, + PROP_SEGMENTATION_SUPPORTED, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_DEVICE_ADDRESS_BINDING, + PROP_DATABASE_REVISION, + -1 +}; + +static const int Device_Properties_Optional[] = { +/* List of Optional properties in this object */ +#if defined(BACDL_MSTP) + PROP_MAX_MASTER, + PROP_MAX_INFO_FRAMES, +#endif + PROP_DESCRIPTION, + PROP_LOCAL_TIME, + PROP_UTC_OFFSET, + PROP_LOCAL_DATE, + PROP_DAYLIGHT_SAVINGS_STATUS, + PROP_LOCATION, +#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0) + PROP_ACTIVE_COV_SUBSCRIPTIONS, +#endif +#if defined(BACNET_TIME_MASTER) + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_TIME_SYNCHRONIZATION_INTERVAL, + PROP_ALIGN_INTERVALS, + PROP_INTERVAL_OFFSET, +#endif + PROP_SERIAL_NUMBER, + PROP_TIME_OF_DEVICE_RESTART, + -1 +}; + +static const int Device_Properties_Proprietary[] = { + /* List of Proprietary properties in this object */ + -1 +}; + +/** + * @brief Returns the list of required, optional, and proprietary properties + * for the Device object. + * @param pRequired [out] Pointer to the list of required properties + * @param pOptional [out] Pointer to the list of optional properties + * @param pProprietary [out] Pointer to the list of proprietary properties + * @note The lists are terminated with -1. + * @note The lists are not allocated, so do not free them. + * @note The lists are static, so do not modify them. + * @ingroup ObjIntf + */ void Device_Property_Lists( const int **pRequired, const int **pOptional, const int **pProprietary) { @@ -976,6 +1004,45 @@ bool Device_Objects_Property_List_Member( return found; } +/* note: you really only need to define variables for + properties that are writable or that may change. + The properties that are constant can be hard coded + into the read-property encoding. */ + +/* local data */ +static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE; +static BACNET_CHARACTER_STRING My_Object_Name; +static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL; +static const char *Device_Name_Default = BACNET_DEVICE_OBJECT_NAME; +static const char *Device_Vendor_Name_Default = BACNET_VENDOR_NAME; +static uint16_t Vendor_Identifier = BACNET_VENDOR_ID; +static const char *Model_Name = BACNET_DEVICE_MODEL_NAME; +static const char *Application_Software_Version = BACNET_DEVICE_VERSION; +static const char *Firmware_Revision = BACNET_VERSION_TEXT; +static const char *Device_Location_Default = BACNET_DEVICE_LOCATION_NAME; +static const char *Device_Description_Default = BACNET_DEVICE_DESCRIPTION; +static uint32_t Database_Revision; +static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE; +static BACNET_CHARACTER_STRING Reinit_Password; +static write_property_function Device_Write_Property_Store_Callback; +static uint8_t Device_UUID[16]; +static const char *Serial_Number = BACNET_DEVICE_SERIAL_NUMBER; +static BACNET_TIMESTAMP Time_Of_Device_Restart; +static BACNET_TIME Local_Time; /* rely on OS, if there is one */ +static BACNET_DATE Local_Date; /* rely on OS, if there is one */ +/* NOTE: BACnet UTC Offset is inverse of common practice. + If your UTC offset is -5hours of GMT, + then BACnet UTC offset is +5hours. + BACnet UTC offset is expressed in minutes. */ +static int16_t UTC_Offset = 5 * 60; +static bool Daylight_Savings_Status = false; /* rely on OS */ +#if defined(BACNET_TIME_MASTER) +static bool Align_Intervals; +static uint32_t Interval_Minutes; +static uint32_t Interval_Offset_Minutes; +/* Time_Synchronization_Recipients */ +#endif + /** * @brief Sets the ReinitializeDevice password * @@ -1012,6 +1079,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) { bool status = false; bool password_success = false; + unsigned i; /* From 16.4.1.1.2 Password This optional parameter shall be a CharacterString of up to @@ -1059,6 +1127,15 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; } break; + case BACNET_REINIT_ACTIVATE_CHANGES: + /* note: activate changes *after* the simple ack is sent */ + for (i = 0; i < Network_Port_Count(); i++) { + Network_Port_Changes_Pending_Activate( + Network_Port_Index_To_Instance(i)); + } + Reinitialize_State = rd_data->state; + status = true; + break; default: rd_data->error_class = ERROR_CLASS_SERVICES; rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; @@ -1091,6 +1168,35 @@ uint32_t Device_Index_To_Instance(unsigned index) return Object_Instance_Number; } +/** Return the Object Instance number for our (single) Device Object. + * This is a key function, widely invoked by the handler code, since + * it provides "our" (ie, local) address. + * @ingroup ObjIntf + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number(void) +{ + return Object_Instance_Number; +} + +bool Device_Set_Object_Instance_Number(uint32_t object_id) +{ + bool status = true; /* return value */ + + if (object_id <= BACNET_MAX_INSTANCE) { + Object_Instance_Number = object_id; + } else { + status = false; + } + + return status; +} + +bool Device_Valid_Object_Instance_Number(uint32_t object_id) +{ + return (Object_Instance_Number == object_id); +} + bool Device_Object_Name( uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) { @@ -1103,6 +1209,19 @@ bool Device_Object_Name( return status; } +bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; /*return value */ + + if (!characterstring_same(&My_Object_Name, object_name)) { + /* Make the change and update the database revision */ + status = characterstring_copy(&My_Object_Name, object_name); + Device_Inc_Database_Revision(); + } + + return status; +} + /** * @brief Initialize the Device Object Name with an ANSI C string * @param value [in] The object name as a null-terminated string @@ -1122,19 +1241,6 @@ char *Device_Object_Name_ANSI(void) return (char *)characterstring_value(&My_Object_Name); } -bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name) -{ - bool status = false; /*return value */ - - if (!characterstring_same(&My_Object_Name, object_name)) { - /* Make the change and update the database revision */ - status = characterstring_copy(&My_Object_Name, object_name); - Device_Inc_Database_Revision(); - } - - return status; -} - /** * @brief Initialize a UUID for storing the unique identifier of this device * @note A Universally Unique IDentifier (UUID) - also called a @@ -1200,35 +1306,6 @@ void Device_UUID_Get(uint8_t *uuid, size_t length) } } -/** Return the Object Instance number for our (single) Device Object. - * This is a key function, widely invoked by the handler code, since - * it provides "our" (ie, local) address. - * @ingroup ObjIntf - * @return The Instance number used in the BACNET_OBJECT_ID for the Device. - */ -uint32_t Device_Object_Instance_Number(void) -{ - return Object_Instance_Number; -} - -bool Device_Set_Object_Instance_Number(uint32_t object_id) -{ - bool status = true; /* return value */ - - if (object_id <= BACNET_MAX_INSTANCE) { - Object_Instance_Number = object_id; - } else { - status = false; - } - - return status; -} - -bool Device_Valid_Object_Instance_Number(uint32_t object_id) -{ - return (Object_Instance_Number == object_id); -} - BACNET_DEVICE_STATUS Device_System_Status(void) { return System_Status; @@ -1248,6 +1325,19 @@ int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) return result; } +const char *Device_Vendor_Name(void) +{ + return Device_Vendor_Name_Default; +} + +bool Device_Set_Vendor_Name(const char *name, size_t length) +{ + (void)length; + Device_Vendor_Name_Default = name ? name : BACNET_VENDOR_NAME; + + return true; +} + uint16_t Device_Vendor_Identifier(void) { return Vendor_Identifier; @@ -1322,19 +1412,6 @@ bool Device_Set_Location(const char *name, size_t length) return true; } -const char *Device_Vendor_Name(void) -{ - return Device_Vendor_Name_Default; -} - -bool Device_Set_Vendor_Name(const char *name, size_t length) -{ - (void)length; - Device_Vendor_Name_Default = name ? name : BACNET_VENDOR_NAME; - - return true; -} - /** * @brief Get the device serial-number property value. * @return The device serial-number, as a character string. @@ -1375,6 +1452,16 @@ bool Device_Set_Time_Of_Restart(const BACNET_TIMESTAMP *time_of_restart) return status; } +uint8_t Device_Protocol_Version(void) +{ + return BACNET_PROTOCOL_VERSION; +} + +uint8_t Device_Protocol_Revision(void) +{ + return BACNET_PROTOCOL_REVISION; +} + BACNET_SEGMENTATION Device_Segmentation_Supported(void) { return SEGMENTATION_NONE; @@ -1419,7 +1506,7 @@ unsigned Device_Object_List_Count(void) struct object_functions *pObject = NULL; /* initialize the default return values */ - pObject = &Object_Table[0]; + pObject = Object_Table; while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { if (pObject->Object_Count) { count += pObject->Object_Count(); @@ -1591,6 +1678,111 @@ bool Device_Object_Name_Copy( return found; } +static void Update_Current_Time(void) +{ + datetime_local( + &Local_Date, &Local_Time, &UTC_Offset, &Daylight_Savings_Status); +} + +void Device_getCurrentDateTime(BACNET_DATE_TIME *DateTime) +{ + Update_Current_Time(); + + DateTime->date = Local_Date; + DateTime->time = Local_Time; +} + +int32_t Device_UTC_Offset(void) +{ + Update_Current_Time(); + + return UTC_Offset; +} + +void Device_UTC_Offset_Set(int16_t offset) +{ + UTC_Offset = offset; +} + +bool Device_Daylight_Savings_Status(void) +{ + return Daylight_Savings_Status; +} + +#if defined(BACNET_TIME_MASTER) +/** + * Sets the time sync interval in minutes + * + * @param flag + * This property, of type BOOLEAN, specifies whether (TRUE) + * or not (FALSE) clock-aligned periodic time synchronization is + * enabled. If periodic time synchronization is enabled and the + * time synchronization interval is a factor of (divides without + * remainder) an hour or day, then the beginning of the period + * specified for time synchronization shall be aligned to the hour or + * day, respectively. If this property is present, it shall be writable. + */ +bool Device_Align_Intervals_Set(bool flag) +{ + Align_Intervals = flag; + + return true; +} + +bool Device_Align_Intervals(void) +{ + return Align_Intervals; +} + +/** + * Sets the time sync interval in minutes + * + * @param minutes + * This property, of type Unsigned, specifies the periodic + * interval in minutes at which TimeSynchronization and + * UTCTimeSynchronization requests shall be sent. If this + * property has a value of zero, then periodic time synchronization is + * disabled. If this property is present, it shall be writable. + */ +bool Device_Time_Sync_Interval_Set(uint32_t minutes) +{ + Interval_Minutes = minutes; + + return true; +} + +uint32_t Device_Time_Sync_Interval(void) +{ + return Interval_Minutes; +} + +/** + * Sets the time sync interval offset value. + * + * @param minutes + * This property, of type Unsigned, specifies the offset in + * minutes from the beginning of the period specified for time + * synchronization until the actual time synchronization requests + * are sent. The offset used shall be the value of Interval_Offset + * modulo the value of Time_Synchronization_Interval; + * e.g., if Interval_Offset has the value 31 and + * Time_Synchronization_Interval is 30, the offset used shall be 1. + * Interval_Offset shall have no effect if Align_Intervals is + * FALSE. If this property is present, it shall be writable. + */ +bool Device_Interval_Offset_Set(uint32_t minutes) +{ + Interval_Offset_Minutes = minutes; + + return true; +} + +uint32_t Device_Interval_Offset(void) +{ + return Interval_Offset_Minutes; +} +#endif + /* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or BACNET_STATUS_ABORT for abort message */ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) @@ -1598,10 +1790,6 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) int apdu_len = 0; /* return value */ BACNET_BIT_STRING bit_string = { 0 }; BACNET_CHARACTER_STRING char_string = { 0 }; - BACNET_DATE bdate; - BACNET_TIME btime; - int16_t utc_offset_minutes; - bool dst_active; uint32_t i = 0; uint32_t count = 0; uint8_t *apdu = NULL; @@ -1615,16 +1803,22 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu = rpdata->application_data; apdu_max = rpdata->application_data_len; switch ((int)rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], OBJECT_DEVICE, Object_Instance_Number); + break; + case PROP_OBJECT_NAME: + apdu_len = + encode_application_character_string(&apdu[0], &My_Object_Name); + break; + case PROP_OBJECT_TYPE: + apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE); + break; case PROP_DESCRIPTION: characterstring_init_ansi(&char_string, Device_Description_Default); apdu_len = encode_application_character_string(&apdu[0], &char_string); break; - case PROP_LOCATION: - characterstring_init_ansi(&char_string, Device_Location_Default); - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - break; case PROP_SYSTEM_STATUS: apdu_len = encode_application_enumerated(&apdu[0], Device_System_Status()); @@ -1653,13 +1847,35 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_character_string(&apdu[0], &char_string); break; - case PROP_PROTOCOL_VERSION: + case PROP_LOCATION: + characterstring_init_ansi(&char_string, Device_Location_Default); apdu_len = - encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_VERSION); + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_LOCAL_TIME: + Update_Current_Time(); + apdu_len = encode_application_time(&apdu[0], &Local_Time); + break; + case PROP_UTC_OFFSET: + Update_Current_Time(); + apdu_len = encode_application_signed(&apdu[0], UTC_Offset); + break; + case PROP_LOCAL_DATE: + Update_Current_Time(); + apdu_len = encode_application_date(&apdu[0], &Local_Date); + break; + case PROP_DAYLIGHT_SAVINGS_STATUS: + Update_Current_Time(); + apdu_len = + encode_application_boolean(&apdu[0], Daylight_Savings_Status); + break; + case PROP_PROTOCOL_VERSION: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Protocol_Version()); break; case PROP_PROTOCOL_REVISION: - apdu_len = - encode_application_unsigned(&apdu[0], BACNET_PROTOCOL_REVISION); + apdu_len = encode_application_unsigned( + &apdu[0], Device_Protocol_Revision()); break; case PROP_PROTOCOL_SERVICES_SUPPORTED: /* Note: list of services that are executed, not initiated. */ @@ -1674,17 +1890,19 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) break; case PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED: /* Note: this is the list of objects that can be in this device, - not a list of objects that this device can access */ + not a list of objects that this device can access */ bitstring_init(&bit_string); for (i = 0; i < MAX_ASHRAE_OBJECT_TYPE; i++) { /* initialize all the object types to not-supported */ bitstring_set_bit(&bit_string, (uint8_t)i, false); } /* set the object types with objects to supported */ + pObject = Object_Table; while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { if ((pObject->Object_Count) && (pObject->Object_Count() > 0)) { - bitstring_set_bit(&bit_string, pObject->Object_Type, true); + bitstring_set_bit( + &bit_string, (uint8_t)pObject->Object_Type, true); } pObject++; } @@ -1717,7 +1935,7 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_unsigned(&apdu[0], apdu_retries()); break; case PROP_DEVICE_ADDRESS_BINDING: - /* FIXME: encode the list here, if it exists */ + apdu_len = address_list_encode(&apdu[0], apdu_max); break; case PROP_DATABASE_REVISION: apdu_len = encode_application_unsigned( @@ -1733,22 +1951,28 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) encode_application_unsigned(&apdu[0], dlmstp_max_master()); break; #endif - case PROP_LOCAL_TIME: - datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); - apdu_len = encode_application_time(&apdu[0], &btime); +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + apdu_len = handler_timesync_encode_recipients(&apdu[0], MAX_APDU); + if (apdu_len < 0) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + apdu_len = BACNET_STATUS_ABORT; + } break; - case PROP_LOCAL_DATE: - datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); - apdu_len = encode_application_date(&apdu[0], &bdate); + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Time_Sync_Interval()); break; - case PROP_UTC_OFFSET: - datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); - apdu_len = encode_application_signed(&apdu[0], utc_offset_minutes); + case PROP_ALIGN_INTERVALS: + apdu_len = + encode_application_boolean(&apdu[0], Device_Align_Intervals()); break; - case PROP_DAYLIGHT_SAVINGS_STATUS: - datetime_local(&bdate, &btime, &utc_offset_minutes, &dst_active); - apdu_len = encode_application_boolean(&apdu[0], dst_active); + case PROP_INTERVAL_OFFSET: + apdu_len = + encode_application_unsigned(&apdu[0], Device_Interval_Offset()); break; +#endif #if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0) case PROP_ACTIVE_COV_SUBSCRIPTIONS: if ((apdu_len = handler_cov_encode_subscriptions( @@ -1774,13 +1998,6 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = BACNET_STATUS_ERROR; break; } - /* only array properties can have array options */ - if ((apdu_len >= 0) && (rpdata->object_property != PROP_OBJECT_LIST) && - (rpdata->array_index != BACNET_ARRAY_ALL)) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } return apdu_len; } @@ -1793,7 +2010,7 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) * @return The length of the APDU on success, else BACNET_STATUS_ERROR */ static int Read_Property_Common( - struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) + const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) { int apdu_len = BACNET_STATUS_ERROR; BACNET_CHARACTER_STRING char_string; @@ -1807,64 +2024,24 @@ static int Read_Property_Common( return 0; } apdu = rpdata->application_data; - switch (rpdata->object_property) { - case PROP_OBJECT_IDENTIFIER: - /* only array properties can have array options */ - if (rpdata->array_index != BACNET_ARRAY_ALL) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } else { - /* Device Object exception: requested instance - may not match our instance if a wildcard */ - if (rpdata->object_type == OBJECT_DEVICE) { - rpdata->object_instance = Object_Instance_Number; - } - apdu_len = encode_application_object_id( - &apdu[0], rpdata->object_type, rpdata->object_instance); - } - break; - case PROP_OBJECT_NAME: - /* only array properties can have array options */ - if (rpdata->array_index != BACNET_ARRAY_ALL) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } else { - characterstring_init_ansi(&char_string, ""); - if (pObject->Object_Name) { - (void)pObject->Object_Name( - rpdata->object_instance, &char_string); - } - apdu_len = - encode_application_character_string(&apdu[0], &char_string); - } - break; - case PROP_OBJECT_TYPE: - /* only array properties can have array options */ - if (rpdata->array_index != BACNET_ARRAY_ALL) { - rpdata->error_class = ERROR_CLASS_PROPERTY; - rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - apdu_len = BACNET_STATUS_ERROR; - } else { - apdu_len = encode_application_enumerated( - &apdu[0], rpdata->object_type); - } - break; + if (property_list_common(rpdata->object_property)) { + apdu_len = property_list_common_encode(rpdata, Object_Instance_Number); + } else if (rpdata->object_property == PROP_OBJECT_NAME) { + characterstring_init_ansi(&char_string, ""); + if (pObject->Object_Name) { + (void)pObject->Object_Name(rpdata->object_instance, &char_string); + } + apdu_len = encode_application_character_string(&apdu[0], &char_string); #if (BACNET_PROTOCOL_REVISION >= 14) - case PROP_PROPERTY_LIST: - Device_Objects_Property_List( - rpdata->object_type, rpdata->object_instance, &property_list); - apdu_len = property_list_encode( - rpdata, property_list.Required.pList, - property_list.Optional.pList, property_list.Proprietary.pList); - break; + } else if (rpdata->object_property == PROP_PROPERTY_LIST) { + Device_Objects_Property_List( + rpdata->object_type, rpdata->object_instance, &property_list); + apdu_len = property_list_encode( + rpdata, property_list.Required.pList, property_list.Optional.pList, + property_list.Proprietary.pList); #endif - default: - if (pObject->Object_Read_Property) { - apdu_len = pObject->Object_Read_Property(rpdata); - } - break; + } else if (pObject->Object_Read_Property) { + apdu_len = pObject->Object_Read_Property(rpdata); } return apdu_len; @@ -1888,7 +2065,7 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) rpdata->error_class = ERROR_CLASS_OBJECT; rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; pObject = Device_Objects_Find_Functions(rpdata->object_type); - if (pObject) { + if (pObject != NULL) { if (pObject->Object_Valid_Instance && pObject->Object_Valid_Instance(rpdata->object_instance)) { apdu_len = Read_Property_Common(pObject, rpdata); @@ -1907,109 +2084,224 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) /* returns true if successful */ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) { - bool status = false; /* return value - false=error */ + bool status = false; /* return value */ int len = 0; - uint8_t encoding = 0; - size_t length = 0; - BACNET_APPLICATION_DATA_VALUE value; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + int result = 0; +#if defined(BACNET_TIME_MASTER) + uint32_t minutes = 0; +#endif /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); - /* FIXME: len < application_data_len: more data? */ + len = bacapp_decode_known_property( + wp_data->application_data, wp_data->application_data_len, &value, + wp_data->object_type, wp_data->object_property); if (len < 0) { /* error while decoding - a value larger than we can handle */ wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; return false; } - if ((wp_data->object_property != PROP_OBJECT_LIST) && - (wp_data->array_index != BACNET_ARRAY_ALL)) { - /* only array properties can have array options */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } - switch ((int)wp_data->object_property) { + /* FIXME: len < application_data_len: more data? */ + switch (wp_data->object_property) { case PROP_OBJECT_IDENTIFIER: - if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) { + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_OBJECT_ID); + if (status) { if ((value.type.Object_Id.type == OBJECT_DEVICE) && (Device_Set_Object_Instance_Number( value.type.Object_Id.instance))) { /* we could send an I-Am broadcast to let the world know */ + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_NUMBER_OF_APDU_RETRIES: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + apdu_retries_set((uint8_t)value.type.Unsigned_Int); + } + break; + case PROP_APDU_TIMEOUT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + apdu_timeout_set((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_VENDOR_IDENTIFIER: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + /* FIXME: bounds check? */ + Device_Set_Vendor_Identifier((uint16_t)value.type.Unsigned_Int); + } + break; + case PROP_SYSTEM_STATUS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + result = Device_Set_System_Status( + (BACNET_DEVICE_STATUS)value.type.Enumerated, false); + if (result != 0) { + /* result: - 0 = ok, -1 = bad value, -2 = not allowed */ + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + if (result == -1) { + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + wp_data->error_code = + ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + } + } + } + break; + case PROP_OBJECT_NAME: + status = write_property_string_valid( + wp_data, &value, characterstring_capacity(&My_Object_Name)); + if (status) { + /* All the object names in a device must be unique */ + if (Device_Valid_Object_Name( + &value.type.Character_String, &object_type, + &object_instance)) { + if ((object_type == wp_data->object_type) && + (object_instance == wp_data->object_instance)) { + /* writing same name to same object */ + status = true; + } else { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; + } + } else { + Device_Set_Object_Name(&value.type.Character_String); + } + } + break; + case PROP_LOCATION: + status = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_LOC_LEN); + if (status) { + Device_Set_Location( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + + case PROP_DESCRIPTION: + status = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_DESC_LEN); + if (status) { + Device_Set_Description( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; + case PROP_MODEL_NAME: + status = write_property_empty_string_valid( + wp_data, &value, MAX_DEV_MOD_LEN); + if (status) { + Device_Set_Model_Name( + characterstring_value(&value.type.Character_String), + characterstring_length(&value.type.Character_String)); + } + break; +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Time_Sync_Interval_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_ALIGN_INTERVALS: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } + break; + case PROP_INTERVAL_OFFSET: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int < 65535) { + minutes = value.type.Unsigned_Int; + Device_Interval_Offset_Set(minutes); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; +#endif + case PROP_UTC_OFFSET: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_SIGNED_INT); + if (status) { + if ((value.type.Signed_Int < (12 * 60)) && + (value.type.Signed_Int > (-12 * 60))) { + Device_UTC_Offset_Set(value.type.Signed_Int); status = true; } else { wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; } break; #if defined(BACDL_MSTP) case PROP_MAX_INFO_FRAMES: - if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { if (value.type.Unsigned_Int <= 255) { - dlmstp_set_max_info_frames(value.type.Unsigned_Int); - status = true; + dlmstp_set_max_info_frames( + (uint8_t)value.type.Unsigned_Int); } else { + status = false; wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; } break; case PROP_MAX_MASTER: - if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { if ((value.type.Unsigned_Int > 0) && (value.type.Unsigned_Int <= 127)) { - dlmstp_set_max_master(value.type.Unsigned_Int); - status = true; + dlmstp_set_max_master((uint8_t)value.type.Unsigned_Int); } else { + status = false; wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; } break; #endif - case PROP_OBJECT_NAME: - if (value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) { - length = characterstring_length(&value.type.Character_String); - if (length < 1) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } else if (length < characterstring_capacity(&My_Object_Name)) { - encoding = - characterstring_encoding(&value.type.Character_String); - if (encoding < MAX_CHARACTER_STRING_ENCODING) { - /* All the object names in a device must be unique. */ - if (Device_Valid_Object_Name( - &value.type.Character_String, NULL, NULL)) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_DUPLICATE_NAME; - } else { - Device_Set_Object_Name( - &value.type.Character_String); - status = true; - } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = - ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; - } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; - } - } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + case PROP_TIME_OF_DEVICE_RESTART: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_TIMESTAMP); + if (status) { + bacapp_timestamp_copy( + &Time_Of_Device_Restart, &value.type.Time_Stamp); } break; default: @@ -2024,8 +2316,6 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } break; } - /* not using len at this time */ - (void)len; return status; } @@ -2046,17 +2336,11 @@ static bool Device_Write_Property_Object_Name( BACNET_OBJECT_TYPE object_type = OBJECT_NONE; uint32_t object_instance = 0; int apdu_size = 0; - uint8_t *apdu = NULL; + const uint8_t *apdu = NULL; if (!wp_data) { return false; } - if (wp_data->array_index != BACNET_ARRAY_ALL) { - /* only array properties can have array options */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } apdu = wp_data->application_data; apdu_size = wp_data->application_data_len; len = bacnet_character_string_application_decode(apdu, apdu_size, &value); @@ -2081,8 +2365,8 @@ static bool Device_Write_Property_Object_Name( if (Device_Valid_Object_Name(&value, &object_type, &object_instance)) { if ((object_type == wp_data->object_type) && (object_instance == wp_data->object_instance)) { - /* writing same name to same object */ - status = true; + /* writing same name to same object - but is it writable? */ + status = Object_Write_Property(wp_data); } else { /* name already exists in some object */ wp_data->error_class = ERROR_CLASS_PROPERTY; @@ -2127,14 +2411,14 @@ static void Device_Write_Property_Store(BACNET_WRITE_PROPERTY_DATA *wp_data) */ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) { - bool status = false; + bool status = false; /* Ever the pessimist! */ struct object_functions *pObject = NULL; /* initialize the default return values */ wp_data->error_class = ERROR_CLASS_OBJECT; wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; pObject = Device_Objects_Find_Functions(wp_data->object_type); - if (pObject) { + if (pObject != NULL) { if (pObject->Object_Valid_Instance && pObject->Object_Valid_Instance(wp_data->object_instance)) { if (pObject->Object_Write_Property) { @@ -2142,7 +2426,7 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) if (wp_data->object_property == PROP_PROPERTY_LIST) { wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - return status; + return false; } #endif if (wp_data->object_property == PROP_OBJECT_NAME) { @@ -2155,8 +2439,17 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) Device_Write_Property_Store(wp_data); } } else { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + if (Device_Objects_Property_List_Member( + wp_data->object_type, wp_data->object_instance, + wp_data->object_property)) { + /* this property is not writable */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + /* this property is not supported */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } } } else { wp_data->error_class = ERROR_CLASS_OBJECT; @@ -2167,6 +2460,74 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; } + return (status); +} + +/** + * @brief AddListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Add_List_Element) { + status = pObject->Object_Add_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * @brief RemoveListElement from an object list property + * @param list_element [in] Pointer to the BACnet_List_Element_Data structure, + * which is packed with the information from the request. + * @return The length of the apdu encoded or #BACNET_STATUS_ERROR or + * #BACNET_STATUS_ABORT or #BACNET_STATUS_REJECT. + */ +int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) +{ + int status = BACNET_STATUS_ERROR; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(list_element->object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(list_element->object_instance)) { + if (pObject->Object_Remove_List_Element) { + status = pObject->Object_Remove_List_Element(list_element); + } else { + list_element->error_class = ERROR_CLASS_PROPERTY; + list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + list_element->error_class = ERROR_CLASS_OBJECT; + list_element->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + return status; } @@ -2184,7 +2545,7 @@ bool Device_Encode_Value_List( uint32_t object_instance, BACNET_PROPERTY_VALUE *value_list) { - bool status = false; /* Ever the pessamist! */ + bool status = false; /* Ever the pessimist! */ struct object_functions *pObject = NULL; pObject = Device_Objects_Find_Functions(object_type); @@ -2246,30 +2607,108 @@ void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) } /** - * @brief Updates all the object timers with elapsed milliseconds - * @param milliseconds - number of milliseconds elapsed + * @brief Creates a child object, if supported + * @ingroup ObjHelpers + * @param data - CreateObject data, including error codes if failures + * @return true if object has been created */ -void Device_Timer(uint16_t milliseconds) +bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data) { - struct object_functions *pObject; - unsigned count = 0; - uint32_t instance; + bool status = false; + struct object_functions *pObject = NULL; + uint32_t object_instance; - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Count) { - count = pObject->Object_Count(); - } - while (count) { - count--; - if ((pObject->Object_Timer) && - (pObject->Object_Index_To_Instance)) { - instance = pObject->Object_Index_To_Instance(count); - pObject->Object_Timer(instance, milliseconds); + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Create) { + /* The device supports the object type and may have + sufficient space, but does not support the creation of the + object for some other reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED; + } else if ( + pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being created already exists */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS; + } else { + if (data->list_of_initial_values) { + /* FIXME: add support for writing to list of initial values */ + /* A property specified by the Property_Identifier in the + List of Initial Values does not support initialization + during the CreateObject service. */ + data->first_failed_element_number = 1; + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + /* and the object shall not be created */ + } else { + object_instance = pObject->Object_Create(data->object_instance); + if (object_instance == BACNET_MAX_INSTANCE) { + /* The device cannot allocate the space needed + for the new object.*/ + data->error_class = ERROR_CLASS_RESOURCES; + data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + } else { + /* required by ACK */ + data->object_instance = object_instance; + Device_Inc_Database_Revision(); + status = true; + } } } - pObject++; + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; } + + return status; +} + +/** + * @brief Deletes a child object, if supported + * @ingroup ObjHelpers + * @param data - DeleteObject data, including error codes if failures + * @return true if object has been deleted + */ +bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Delete) { + /* The device supports the object type + but does not support the deletion of the + object for some reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } else if ( + pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being deleted must already exist */ + status = pObject->Object_Delete(data->object_instance); + if (status) { + Device_Inc_Database_Revision(); + } else { + /* The object exists but cannot be deleted. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } + } else { + /* The object to be deleted does not exist. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } else { + /* The device does not support the specified object type. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + } + + return status; } /** Looks up the requested Object to see if the functionality is supported. @@ -2304,11 +2743,13 @@ void Device_Init(object_functions_t *object_table) { struct object_functions *pObject = NULL; - /* we don't use the object table passed in - since there is extra stuff we don't need in there. */ - (void)object_table; - /* our local object table */ - pObject = &Object_Table[0]; + datetime_init(); + if (object_table) { + Object_Table = object_table; + } else { + Object_Table = &My_Object_Table[0]; + } + pObject = Object_Table; while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { if (pObject->Object_Init) { pObject->Object_Init(); @@ -2320,4 +2761,86 @@ void Device_Init(object_functions_t *object_table) Object_Instance_Number = BACNET_MAX_INSTANCE; } characterstring_init_ansi(&My_Object_Name, Device_Name_Default); +#if (BACNET_PROTOCOL_REVISION >= 14) + Channel_Write_Property_Internal_Callback_Set(Device_Write_Property); +#endif +} + +bool DeviceGetRRInfo( + BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ + RR_PROP_INFO *pInfo) +{ /* Where to put the response */ + bool status = false; /* return value */ + + switch (pRequest->object_property) { + case PROP_VT_CLASSES_SUPPORTED: + case PROP_ACTIVE_VT_SESSIONS: + case PROP_LIST_OF_SESSION_KEYS: + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: + case PROP_MANUAL_SLAVE_ADDRESS_BINDING: + case PROP_SLAVE_ADDRESS_BINDING: + case PROP_RESTART_NOTIFICATION_RECIPIENTS: + case PROP_UTC_TIME_SYNCHRONIZATION_RECIPIENTS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + if (pRequest->array_index == BACNET_ARRAY_ALL) { + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } else { + pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + } + break; + + case PROP_DEVICE_ADDRESS_BINDING: + pInfo->RequestTypes = RR_BY_POSITION; + pInfo->Handler = rr_address_list_encode; + status = true; + break; + + case PROP_ACTIVE_COV_SUBSCRIPTIONS: + pInfo->RequestTypes = RR_BY_POSITION; + pRequest->error_class = ERROR_CLASS_PROPERTY; + if (pRequest->array_index == BACNET_ARRAY_ALL) { + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } else { + pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + } + break; + default: + pRequest->error_class = ERROR_CLASS_SERVICES; + pRequest->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST; + if (pRequest->array_index == BACNET_ARRAY_ALL) { + pRequest->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + pRequest->error_class = ERROR_CLASS_PROPERTY; + } + break; + } + + return status; +} + +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer(uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6053a59a..3f4de766 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -180,6 +180,8 @@ list(APPEND testdirs bacnet/basic/object/trendlog # basic/program bacnet/basic/program/ubasic + # basic/server + bacnet/basic/server/bacnet_device # basic/sys bacnet/basic/sys/bramfs bacnet/basic/sys/bsramfs diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index ce21173d..702e0d6b 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -81,16 +81,12 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/structured_view.c ${SRC_DIR}/bacnet/basic/object/time_value.c ${SRC_DIR}/bacnet/basic/object/trendlog.c - ${SRC_DIR}/bacnet/basic/service/h_apdu.c - ${SRC_DIR}/bacnet/basic/service/h_cov.c - ${SRC_DIR}/bacnet/basic/service/h_wp.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/basic/sys/lighting_command.c ${SRC_DIR}/bacnet/basic/sys/linear.c - ${SRC_DIR}/bacnet/basic/tsm/tsm.c ${SRC_DIR}/bacnet/datalink/bvlc.c ${SRC_DIR}/bacnet/datalink/bvlc6.c ${SRC_DIR}/bacnet/cov.c @@ -112,9 +108,14 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/special_event.c ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/secure_connect.c - ./stubs.c # Test and test library files ./src/main.c + ${TST_DIR}/bacnet/basic/object/test/apdu_mock.c + ${TST_DIR}/bacnet/basic/object/test/bip_mock.c + ${TST_DIR}/bacnet/basic/object/test/cov_mock.c + ${TST_DIR}/bacnet/basic/object/test/property_test.c + ${TST_DIR}/bacnet/basic/object/test/datetime_local.c + ${TST_DIR}/bacnet/basic/object/test/tsm_mock.c ${ZTST_DIR}/ztest_mock.c ${ZTST_DIR}/ztest.c ) diff --git a/test/bacnet/basic/object/test/apdu_mock.c b/test/bacnet/basic/object/test/apdu_mock.c index 5c3a1760..41f9930d 100644 --- a/test/bacnet/basic/object/test/apdu_mock.c +++ b/test/bacnet/basic/object/test/apdu_mock.c @@ -9,6 +9,34 @@ #include #include +bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported) +{ + (void)service_supported; + return true; +} + +static uint16_t Timeout_Milliseconds = 1000; +uint16_t apdu_timeout(void) +{ + return Timeout_Milliseconds; +} + +void apdu_timeout_set(uint16_t milliseconds) +{ + Timeout_Milliseconds = milliseconds; +} + +static uint8_t Number_Of_Retries = 3; +uint8_t apdu_retries(void) +{ + return Number_Of_Retries; +} + +void apdu_retries_set(uint8_t value) +{ + Number_Of_Retries = value; +} + uint16_t apdu_decode_confirmed_service_request( uint8_t *apdu, uint16_t apdu_len, diff --git a/test/bacnet/basic/object/test/bip_mock.c b/test/bacnet/basic/object/test/bip_mock.c new file mode 100644 index 00000000..0e47e1cb --- /dev/null +++ b/test/bacnet/basic/object/test/bip_mock.c @@ -0,0 +1,28 @@ +/** + * @file + * @brief mock BACnet/IP datalink functions + * @author Steve Karg + * @date September 2025 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include + +void bip_get_my_address(BACNET_ADDRESS *my_address) +{ + (void)my_address; +} + +int bip_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + (void)dest; + (void)npdu_data; + (void)pdu; + (void)pdu_len; + + return 0; +} diff --git a/test/bacnet/basic/object/test/cov_mock.c b/test/bacnet/basic/object/test/cov_mock.c new file mode 100644 index 00000000..20e42355 --- /dev/null +++ b/test/bacnet/basic/object/test/cov_mock.c @@ -0,0 +1,17 @@ +/** + * @file + * @brief mock for COV functions + * @author Steve Karg + * @date September 2025 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include + +int handler_cov_encode_subscriptions(uint8_t *apdu, int max_apdu) +{ + (void)apdu; + (void)max_apdu; + return 0; +} diff --git a/test/bacnet/basic/object/test/property_test.c b/test/bacnet/basic/object/test/property_test.c index d1c5e13d..ac641a43 100644 --- a/test/bacnet/basic/object/test/property_test.c +++ b/test/bacnet/basic/object/test/property_test.c @@ -284,14 +284,14 @@ void bacnet_object_properties_read_write_test( rpdata.object_type = object_type; rpdata.object_instance = object_instance; property_list(&pRequired, &pOptional, &pProprietary); - /* detect properties that are not in the property lists */ + /* detect properties that are missing from the property lists */ for (property = 0; property < MAX_BACNET_PROPERTY_ID; property++) { if (property_lists_member( pRequired, pOptional, pProprietary, property)) { continue; } if ((property == PROP_ALL) || (property == PROP_REQUIRED) || - (property == PROP_OPTIONAL)) { + (property == PROP_OPTIONAL) || (property == PROP_PROPERTY_LIST)) { continue; } rpdata.object_property = property; diff --git a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt new file mode 100644 index 00000000..672cf18d --- /dev/null +++ b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt @@ -0,0 +1,124 @@ +# 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/server/bacnet_device.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/abort.c + ${SRC_DIR}/bacnet/access_rule.c + ${SRC_DIR}/bacnet/arf.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/bacerror.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/binding/address.c + ${SRC_DIR}/bacnet/basic/object/acc.c + ${SRC_DIR}/bacnet/basic/object/ai.c + ${SRC_DIR}/bacnet/basic/object/ao.c + ${SRC_DIR}/bacnet/basic/object/av.c + ${SRC_DIR}/bacnet/basic/object/bi.c + ${SRC_DIR}/bacnet/basic/object/bitstring_value.c + ${SRC_DIR}/bacnet/basic/object/blo.c + ${SRC_DIR}/bacnet/basic/object/bo.c + ${SRC_DIR}/bacnet/basic/object/bv.c + ${SRC_DIR}/bacnet/basic/object/calendar.c + ${SRC_DIR}/bacnet/basic/object/channel.c + ${SRC_DIR}/bacnet/basic/object/color_object.c + ${SRC_DIR}/bacnet/basic/object/color_temperature.c + ${SRC_DIR}/bacnet/basic/object/command.c + ${SRC_DIR}/bacnet/basic/object/csv.c + ${SRC_DIR}/bacnet/basic/object/bacfile.c + ${SRC_DIR}/bacnet/basic/object/iv.c + ${SRC_DIR}/bacnet/basic/object/lc.c + ${SRC_DIR}/bacnet/basic/object/lo.c + ${SRC_DIR}/bacnet/basic/object/lsp.c + ${SRC_DIR}/bacnet/basic/object/lsz.c + ${SRC_DIR}/bacnet/basic/object/ms-input.c + ${SRC_DIR}/bacnet/basic/object/mso.c + ${SRC_DIR}/bacnet/basic/object/msv.c + ${SRC_DIR}/bacnet/basic/object/netport.c + ${SRC_DIR}/bacnet/basic/object/osv.c + ${SRC_DIR}/bacnet/basic/object/piv.c + ${SRC_DIR}/bacnet/basic/object/program.c + ${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/trendlog.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/basic/sys/lighting_command.c + ${SRC_DIR}/bacnet/basic/sys/linear.c + ${SRC_DIR}/bacnet/datalink/bvlc.c + ${SRC_DIR}/bacnet/datalink/bvlc6.c + ${SRC_DIR}/bacnet/cov.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/dcc.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/npdu.c + ${SRC_DIR}/bacnet/proplist.c + ${SRC_DIR}/bacnet/property.c + ${SRC_DIR}/bacnet/reject.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/apdu_mock.c + ${TST_DIR}/bacnet/basic/object/test/bip_mock.c + ${TST_DIR}/bacnet/basic/object/test/cov_mock.c + ${TST_DIR}/bacnet/basic/object/test/property_test.c + ${TST_DIR}/bacnet/basic/object/test/datetime_local.c + ${TST_DIR}/bacnet/basic/object/test/tsm_mock.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/basic/server/bacnet_device/src/main.c b/test/bacnet/basic/server/bacnet_device/src/main.c new file mode 100644 index 00000000..c40c7f71 --- /dev/null +++ b/test/bacnet/basic/server/bacnet_device/src/main.c @@ -0,0 +1,443 @@ +/** + * @file + * @brief test BACnet Device object APIs + * @author Steve Karg + * @date 2004 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test ReadProperty API + */ +static void test_Device_Data_Sharing(void) +{ + const uint32_t instance = 123; + unsigned count; + bool status = false; + uint32_t test_instance; + const int skip_fail_property_list[] = { -1 }; + + Device_Init(NULL); + status = Device_Set_Object_Instance_Number(instance); + zassert_true(status, NULL); + test_instance = Device_Object_Instance_Number(); + zassert_equal(test_instance, instance, NULL); + status = Device_Valid_Object_Instance_Number(BACNET_MAX_INSTANCE); + zassert_false(status, NULL); + count = Device_Count(); + zassert_equal(count, 1, NULL); + test_instance = Device_Index_To_Instance(0); + zassert_equal(test_instance, instance, NULL); + /* perform a general test for RP/WP */ + bacnet_object_properties_read_write_test( + OBJECT_DEVICE, instance, Device_Property_Lists, Device_Read_Property, + Device_Write_Property, skip_fail_property_list); +} + +/** + * @brief Test basic API + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(device_tests, testDevice) +#else +static void testDevice(void) +#endif +{ + bool status = false; + const char *name = "Patricia"; + BACNET_REINITIALIZE_DEVICE_DATA rd_data = { 0 }; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_CREATE_OBJECT_DATA create_data = { 0 }; + BACNET_DELETE_OBJECT_DATA delete_data = { 0 }; + BACNET_LIST_ELEMENT_DATA list_data = { 0 }; + BACNET_PROPERTY_ID property = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + BACNET_CHARACTER_STRING char_string = { 0 }, test_char_string = { 0 }; + uint8_t uuid[16] = { 0 }, test_uuid[16] = { 0 }; + BACNET_TIMESTAMP time_of_restart = { 0 }, test_time_of_restart = { 0 }; + BACNET_DATE_TIME date_time = { 0 }, test_date_time = { 0 }; + const char *name_string = NULL; + unsigned count = 0, test_count = 0; + int len = 0, time_diff = 0; + + Device_Init(NULL); + status = Device_Set_Object_Instance_Number(0); + zassert_equal(Device_Object_Instance_Number(), 0, NULL); + zassert_true(status, NULL); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + zassert_equal(Device_Object_Instance_Number(), BACNET_MAX_INSTANCE, NULL); + zassert_true(status, NULL); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE / 2); + zassert_equal( + Device_Object_Instance_Number(), (BACNET_MAX_INSTANCE / 2), NULL); + zassert_true(status, NULL); + status = Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE + 1); + zassert_not_equal( + Device_Object_Instance_Number(), (BACNET_MAX_INSTANCE + 1), NULL); + zassert_false(status, NULL); + + Device_Set_System_Status(STATUS_NON_OPERATIONAL, true); + zassert_equal(Device_System_Status(), STATUS_NON_OPERATIONAL, NULL); + + zassert_equal(Device_Vendor_Identifier(), BACNET_VENDOR_ID, NULL); + + Device_Set_Model_Name(name, strlen(name)); + zassert_equal(strcmp(Device_Model_Name(), name), 0, NULL); + + /* Reinitialize with no device password */ + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_COLDSTART; + characterstring_init_ansi(&rd_data.password, NULL); + status = Device_Reinitialize_Password_Set(NULL); + status = Device_Reinitialize(&rd_data); + zassert_true(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_DEVICE, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + zassert_equal( + rd_data.error_code, ERROR_CODE_SUCCESS, "error-code=%s", + bactext_error_code_name(rd_data.error_code)); + /* Reinitialize with device valid password, service no password */ + status = Device_Reinitialize_Password_Set("valid"); + zassert_true(status, NULL); + status = characterstring_init_ansi(&rd_data.password, NULL); + zassert_true(status, NULL); + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SECURITY, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + zassert_equal( + rd_data.error_code, ERROR_CODE_PASSWORD_FAILURE, "error-code=%s", + bactext_error_code_name(rd_data.error_code)); + /* Reinitialize with device valid password, service invalid password */ + status = characterstring_init_ansi(&rd_data.password, "invalid"); + zassert_true(status, NULL); + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SECURITY, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + zassert_equal( + rd_data.error_code, ERROR_CODE_PASSWORD_FAILURE, "error-code=%s", + bactext_error_code_name(rd_data.error_code)); + /* Reinitialize with device valid password, service valid password */ + characterstring_init_ansi(&rd_data.password, "valid"); + status = Device_Reinitialize(&rd_data); + zassert_true(status, NULL); + /* Reinitialize with device valid password, service too long password */ + characterstring_init_ansi(&rd_data.password, "abcdefghijklmnopqrstuvwxyz"); + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + zassert_equal( + rd_data.error_code, ERROR_CODE_PARAMETER_OUT_OF_RANGE, "error-code=%s", + bactext_error_code_name(rd_data.error_code)); + /* Reinitialize with device no password, unsupported state */ + status = Device_Reinitialize_Password_Set(NULL); + zassert_true(status, NULL); + rd_data.state = BACNET_REINIT_MAX; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + zassert_equal( + rd_data.error_code, ERROR_CODE_PARAMETER_OUT_OF_RANGE, "error-code=%s", + bactext_error_code_name(rd_data.error_code)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_STARTBACKUP; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_ENDBACKUP; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_STARTRESTORE; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_ENDRESTORE; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_ABORTRESTORE; + status = Device_Reinitialize(&rd_data); + zassert_false(status, NULL); + zassert_equal( + rd_data.error_class, ERROR_CLASS_SERVICES, "error-class=%s", + bactext_error_class_name(rd_data.error_class)); + rd_data.error_class = ERROR_CLASS_DEVICE; + rd_data.error_code = ERROR_CODE_SUCCESS; + rd_data.state = BACNET_REINIT_ACTIVATE_CHANGES; + status = Device_Reinitialize(&rd_data); + zassert_true(status, NULL); + zassert_equal( + rd_data.state, Device_Reinitialized_State(), "state=%d", rd_data.state); + Device_Reinitialize_State_Set(BACNET_REINIT_IDLE); + zassert_equal(Device_Reinitialized_State(), BACNET_REINIT_IDLE, NULL); + /* test Object_List API */ + count = Device_Object_List_Count(); + zassert_true(count > 0, NULL); + status = Device_Object_List_Identifier(0, &object_type, &object_instance); + zassert_false(status, NULL); + status = Device_Object_List_Identifier(1, &object_type, &object_instance); + zassert_true(status, NULL); + status = Device_Valid_Object_Id(object_type, object_instance); + zassert_true(status, NULL); + object_type = OBJECT_DEVICE; + object_instance = Device_Object_Instance_Number(); + for (property = 0; property < MAX_BACNET_PROPERTY_ID; property++) { + if (Device_Objects_Property_List_Member( + object_type, object_instance, property)) { + continue; + } + if ((property == PROP_ALL) || (property == PROP_REQUIRED) || + (property == PROP_OPTIONAL) || (property == PROP_PROPERTY_LIST)) { + continue; + } + rpdata.object_property = property; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Device_Read_Property(&rpdata); + zassert_equal( + len, BACNET_STATUS_ERROR, + "property '%s' array_index=ALL: Missing.\n", + bactext_property_name(rpdata.object_property)); + /* shrink the number space and skip proprietary range values */ + if (property == PROP_RESERVED_RANGE_MAX) { + property = PROP_RESERVED_RANGE_MIN2 - 1; + } + /* shrink the number space to known values */ + if (property == PROP_RESERVED_RANGE_LAST) { + break; + } + } + /* test Object Name API */ + status = Device_Set_Object_Name(NULL); + zassert_false(status, NULL); + characterstring_init_ansi(&char_string, "Teddy"); + status = Device_Set_Object_Name(&char_string); + zassert_true(status, NULL); + Device_Object_Name(Device_Object_Instance_Number(), &test_char_string); + status = characterstring_same(&char_string, &test_char_string); + zassert_true(status, NULL); + Device_Object_Name_ANSI_Init("Tuxedo"); + name_string = Device_Object_Name_ANSI(); + zassert_not_null(name_string, NULL); + zassert_equal(strcmp(name_string, "Tuxedo"), 0, NULL); + status = Device_Object_Name_Copy( + OBJECT_DEVICE, Device_Object_Instance_Number(), &test_char_string); + zassert_true(status, NULL); + status = characterstring_same(&char_string, &test_char_string); + /* UUID tests */ + Device_UUID_Get(NULL, 0); + Device_UUID_Set(NULL, 0); + Device_UUID_Get(test_uuid, sizeof(test_uuid)); + for (int i = 0; i < 16; i++) { + zassert_equal(test_uuid[i], 0, NULL); + } + Device_UUID_Init(); + Device_UUID_Get(uuid, sizeof(uuid)); + status = false; + for (int i = 0; i < 16; i++) { + if (uuid[i] != test_uuid[i]) { + status = true; + } + } + zassert_true(status, NULL); + Device_UUID_Set(test_uuid, sizeof(test_uuid)); + Device_UUID_Get(uuid, sizeof(uuid)); + for (int i = 0; i < 16; i++) { + zassert_equal(uuid[i], test_uuid[i], NULL); + } + name_string = Device_Vendor_Name(); + zassert_not_null(name_string, NULL); + zassert_true(strlen(name_string) > 0, NULL); + status = Device_Set_Vendor_Name(NULL, 0); + zassert_true(status, NULL); + name_string = Device_Firmware_Revision(); + zassert_not_null(name_string, NULL); + zassert_true(strlen(name_string) > 0, NULL); + status = Device_Set_Firmware_Revision(NULL, 0); + zassert_true(status, NULL); + name_string = Device_Description(); + zassert_not_null(name_string, NULL); + zassert_true(strlen(name_string) > 0, NULL); + status = Device_Set_Description(NULL, 0); + zassert_true(status, NULL); + name_string = Device_Location(); + zassert_not_null(name_string, NULL); + zassert_true(strlen(name_string) > 0, NULL); + status = Device_Set_Location(NULL, 0); + zassert_true(status, NULL); + name_string = Device_Serial_Number(); + zassert_not_null(name_string, NULL); + zassert_true(strlen(name_string) > 0, NULL); + status = Device_Serial_Number_Set(NULL, 0); + zassert_true(status, NULL); + Device_Set_Time_Of_Restart(NULL); + time_of_restart.tag = TIME_STAMP_TIME; + time_of_restart.value.time.hour = 1; + time_of_restart.value.time.min = 2; + time_of_restart.value.time.sec = 3; + time_of_restart.value.time.hundredths = 4; + Device_Set_Time_Of_Restart(&time_of_restart); + Device_Time_Of_Restart(&test_time_of_restart); + status = bacapp_timestamp_same(&time_of_restart, &test_time_of_restart); + zassert_true(status, NULL); + Device_Set_Database_Revision(0); + zassert_equal(Device_Database_Revision(), 0, NULL); + Device_Inc_Database_Revision(); + zassert_equal(Device_Database_Revision(), 1, NULL); + Device_Inc_Database_Revision(); + zassert_equal(Device_Database_Revision(), 2, NULL); + Device_getCurrentDateTime(&date_time); + datetime_local(&date_time.date, &date_time.time, NULL, NULL); + time_diff = datetime_compare(&date_time, &test_date_time); + zassert_equal(time_diff, 0, NULL); + Device_UTC_Offset_Set(-60); + time_diff = Device_UTC_Offset(); + zassert_equal(time_diff, -60, NULL); + status = Device_Daylight_Savings_Status(); + zassert_false(status, NULL); + + /* Add/Remove List Elements */ + list_data.object_type = OBJECT_DEVICE; + list_data.object_instance = Device_Object_Instance_Number(); + list_data.object_property = PROP_ACTIVE_COV_SUBSCRIPTIONS; + list_data.array_index = BACNET_ARRAY_ALL; + list_data.application_data = NULL; + list_data.application_data_len = 0; + list_data.first_failed_element_number = 0; + list_data.error_class = ERROR_CLASS_DEVICE; + list_data.error_code = ERROR_CODE_SUCCESS; + len = Device_Add_List_Element(&list_data); + zassert_true(len < 0, NULL); + list_data.object_type = OBJECT_ANALOG_VALUE; + list_data.object_instance = BACNET_MAX_INSTANCE; + len = Device_Add_List_Element(&list_data); + zassert_true(len < 0, NULL); + list_data.object_instance = 1; + len = Device_Add_List_Element(&list_data); + zassert_true(len < 0, NULL); + zassert_equal( + list_data.error_code, ERROR_CODE_UNKNOWN_OBJECT, "error-code=%s", + bactext_error_code_name(list_data.error_code)); + Device_Remove_List_Element(&list_data); + zassert_equal( + list_data.error_code, ERROR_CODE_UNKNOWN_OBJECT, "error-code=%s", + bactext_error_code_name(list_data.error_code)); + list_data.object_type = OBJECT_ANALOG_INPUT; + len = Device_Add_List_Element(&list_data); + zassert_true(len < 0, NULL); + zassert_equal( + list_data.error_code, ERROR_CODE_UNKNOWN_OBJECT, "error-code=%s", + bactext_error_code_name(list_data.error_code)); + + /* COV API tests */ + status = Device_COV(OBJECT_ANALOG_VALUE, BACNET_MAX_INSTANCE); + zassert_false(status, NULL); + Device_COV_Clear(OBJECT_ANALOG_VALUE, BACNET_MAX_INSTANCE); + status = Device_Encode_Value_List( + OBJECT_ANALOG_VALUE, BACNET_MAX_INSTANCE, NULL); + zassert_false(status, NULL); + /* create/delete object tests */ + count = Device_Object_List_Count(); + create_data.object_type = OBJECT_ANALOG_VALUE; + create_data.object_instance = BACNET_MAX_INSTANCE; + create_data.list_of_initial_values = NULL; + create_data.error_class = ERROR_CLASS_DEVICE; + create_data.error_code = ERROR_CODE_SUCCESS; + create_data.first_failed_element_number = 0; + status = Device_Create_Object(&create_data); + zassert_true(status, NULL); + zassert_equal(create_data.error_code, ERROR_CODE_SUCCESS, NULL); + zassert_equal(create_data.object_instance, 1, NULL); + test_count = Device_Object_List_Count(); + zassert_equal(count + 1, test_count, NULL); + delete_data.object_type = create_data.object_type; + delete_data.object_instance = create_data.object_instance; + delete_data.error_class = ERROR_CLASS_DEVICE; + delete_data.error_code = ERROR_CODE_SUCCESS; + status = Device_Delete_Object(&delete_data); + zassert_true(status, NULL); + zassert_equal(delete_data.error_code, ERROR_CODE_SUCCESS, NULL); + test_count = Device_Object_List_Count(); + zassert_equal(count, test_count, NULL); + status = Device_Delete_Object(&delete_data); + zassert_false(status, NULL); + /* known object without delete */ + delete_data.object_type = OBJECT_DEVICE; + delete_data.object_instance = Device_Object_Instance_Number(); + delete_data.error_class = ERROR_CLASS_DEVICE; + delete_data.error_code = ERROR_CODE_SUCCESS; + status = Device_Delete_Object(&delete_data); + zassert_false(status, NULL); + delete_data.object_type = MAX_BACNET_OBJECT_TYPE; + status = Device_Delete_Object(&delete_data); + zassert_false(status, NULL); + /* known object without create */ + create_data.object_type = OBJECT_DEVICE; + create_data.object_instance = BACNET_MAX_INSTANCE; + create_data.list_of_initial_values = NULL; + create_data.error_class = ERROR_CLASS_DEVICE; + create_data.error_code = ERROR_CODE_SUCCESS; + create_data.first_failed_element_number = 0; + status = Device_Create_Object(&create_data); + zassert_false(status, NULL); + create_data.object_type = MAX_BACNET_OBJECT_TYPE; + status = Device_Create_Object(&create_data); + zassert_false(status, NULL); + /* COV value list */ + status = Device_Value_List_Supported(OBJECT_DEVICE); + zassert_false(status, NULL); + /* object timers */ + Device_Timer(1000); +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(device_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite( + device_tests, ztest_unit_test(testDevice), + ztest_unit_test(test_Device_Data_Sharing)); + + ztest_run_test_suite(device_tests); +} +#endif