diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 7e0432ca..67c9d6cb 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1184,7 +1184,8 @@ typedef enum BACnetRestartReason { compilers will allocate adequate sized datatype for enum which is used to store decoding */ RESTART_REASON_PROPRIETARY_MIN = 64, - RESTART_REASON_PROPRIETARY_MAX = 255 + RESTART_REASON_PROPRIETARY_MAX = 255, + BACNET_RESTART_REASON_MAX = 256 } BACNET_RESTART_REASON; typedef enum BACnetPropertyStates { diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 8cfbc867..bb25fd0c 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -1279,6 +1279,7 @@ static const int32_t Device_Properties_Optional[] = { PROP_ACTIVE_COV_SUBSCRIPTIONS, PROP_SERIAL_NUMBER, PROP_TIME_OF_DEVICE_RESTART, + PROP_LAST_RESTART_REASON, #if defined(BACNET_TIME_MASTER) PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, @@ -1331,7 +1332,10 @@ static const int32_t Writable_Properties[] = { PROP_BACKUP_PREPARATION_TIME, PROP_RESTORE_PREPARATION_TIME, #endif +#if defined(BACAPP_TIMESTAMP) PROP_TIME_OF_DEVICE_RESTART, +#endif + PROP_LAST_RESTART_REASON, -1 }; @@ -1434,6 +1438,7 @@ static uint8_t Device_UUID[16]; /* static uint8_t Max_Segments_Accepted = 0; */ /* VT_Classes_Supported */ /* Active_VT_Sessions */ +static BACNET_RESTART_REASON Last_Restart_Reason = RESTART_REASON_UNKNOWN; 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 */ @@ -2058,6 +2063,33 @@ bool Device_Serial_Number_Set(const char *str, size_t length) return status; } +/** + * @brief Set the device last-restart-reason property value. + * @param restart_reason [in] The new device last-restart-reason, as a + * BACNET_RESTART_REASON. + * @return true if the device last-restart-reason was set + */ +bool Device_Last_Restart_Reason_Set(const BACNET_RESTART_REASON restart_reason) +{ + bool status = false; /*return value */ + + if (restart_reason < BACNET_RESTART_REASON_MAX) { + Last_Restart_Reason = restart_reason; + status = true; + } + + return status; +} + +/** + * @brief Get the device last-restart-reason property value. + * @return The device last-restart-reason, as a BACNET_RESTART_REASON. + */ +BACNET_RESTART_REASON Device_Last_Restart_Reason(void) +{ + return Last_Restart_Reason; +} + void Device_Time_Of_Restart(BACNET_TIMESTAMP *time_of_restart) { bacapp_timestamp_copy(time_of_restart, &Time_Of_Device_Restart); @@ -2559,10 +2591,10 @@ static BACNET_ERROR_CODE Device_Configuration_File_Write( (void)object_instance; if (array_index == 0) { /* This array is not required to be resizable - through BACnet write services */ + through BACnet write services */ (void)array_size; error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else { + } else if (array_index <= BACNET_BACKUP_FILE_COUNT) { len = bacnet_object_id_application_decode( application_data, application_data_len, &object_type, &instance); if (len > 0) { @@ -2579,6 +2611,8 @@ static BACNET_ERROR_CODE Device_Configuration_File_Write( } else { error_code = ERROR_CODE_INVALID_DATA_TYPE; } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; } return error_code; @@ -2979,6 +3013,10 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = bacapp_encode_timestamp(&apdu[0], &Time_Of_Device_Restart); break; + case PROP_LAST_RESTART_REASON: + apdu_len = + encode_application_enumerated(&apdu[0], Last_Restart_Reason); + break; default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; @@ -3351,14 +3389,26 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } break; #endif +#if defined(BACAPP_TIMESTAMP) case PROP_TIME_OF_DEVICE_RESTART: status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_TIMESTAMP); if (status) { -#if defined(BACAPP_TIMESTAMP) bacapp_timestamp_copy( &Time_Of_Device_Restart, &value.type.Time_Stamp); + } + break; #endif + case PROP_LAST_RESTART_REASON: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Device_Last_Restart_Reason_Set( + (BACNET_RESTART_REASON)value.type.Unsigned_Int); + if (!status) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } } break; default: diff --git a/src/bacnet/basic/object/device.h b/src/bacnet/basic/object/device.h index 27e9f60d..3ac84b90 100644 --- a/src/bacnet/basic/object/device.h +++ b/src/bacnet/basic/object/device.h @@ -417,6 +417,11 @@ const char *Device_Serial_Number(void); BACNET_STACK_EXPORT bool Device_Serial_Number_Set(const char *name, size_t length); +BACNET_STACK_EXPORT +bool Device_Last_Restart_Reason_Set(const BACNET_RESTART_REASON restart_reason); +BACNET_STACK_EXPORT +BACNET_RESTART_REASON Device_Last_Restart_Reason(void); + BACNET_STACK_EXPORT void Device_Time_Of_Restart(BACNET_TIMESTAMP *time_of_restart); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c index 6b906e7d..f677d89a 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -1215,6 +1215,7 @@ static const int32_t Device_Properties_Optional[] = { #endif PROP_SERIAL_NUMBER, PROP_TIME_OF_DEVICE_RESTART, + PROP_LAST_RESTART_REASON, #if defined(BACNET_TIME_MASTER) PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, @@ -1268,6 +1269,7 @@ static const int32_t Writable_Properties[] = { PROP_RESTORE_PREPARATION_TIME, #endif PROP_TIME_OF_DEVICE_RESTART, + PROP_LAST_RESTART_REASON, -1 }; @@ -1368,6 +1370,7 @@ static list_element_function Device_Add_List_Element_Callback; static list_element_function Device_Remove_List_Element_Callback; static uint8_t Device_UUID[16]; static const char *Serial_Number = BACNET_DEVICE_SERIAL_NUMBER; +static BACNET_RESTART_REASON Last_Restart_Reason = RESTART_REASON_UNKNOWN; 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 */ @@ -1933,6 +1936,33 @@ bool Device_Serial_Number_Set(const char *str, size_t length) return true; } +/** + * @brief Set the device last-restart-reason property value. + * @param restart_reason [in] The new device last-restart-reason, as a + * BACNET_RESTART_REASON. + * @return true if the device last-restart-reason was set + */ +bool Device_Last_Restart_Reason_Set(const BACNET_RESTART_REASON restart_reason) +{ + bool status = false; /*return value */ + + if (restart_reason < BACNET_RESTART_REASON_MAX) { + Last_Restart_Reason = restart_reason; + status = true; + } + + return status; +} + +/** + * @brief Get the device last-restart-reason property value. + * @return The device last-restart-reason, as a BACNET_RESTART_REASON. + */ +BACNET_RESTART_REASON Device_Last_Restart_Reason(void) +{ + return Last_Restart_Reason; +} + void Device_Time_Of_Restart(BACNET_TIMESTAMP *time_of_restart) { bacapp_timestamp_copy(time_of_restart, &Time_Of_Device_Restart); @@ -2843,6 +2873,10 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = bacapp_encode_timestamp(&apdu[0], &Time_Of_Device_Restart); break; + case PROP_LAST_RESTART_REASON: + apdu_len = + encode_application_enumerated(&apdu[0], Last_Restart_Reason); + break; default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; @@ -3225,6 +3259,18 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) #endif } break; + case PROP_LAST_RESTART_REASON: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Device_Last_Restart_Reason_Set( + (BACNET_RESTART_REASON)value.type.Unsigned_Int); + if (!status) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; default: if (property_lists_member( Device_Properties_Required, Device_Properties_Optional, diff --git a/test/bacnet/basic/server/bacnet_device/src/main.c b/test/bacnet/basic/server/bacnet_device/src/main.c index 2c2796dc..c565308f 100644 --- a/test/bacnet/basic/server/bacnet_device/src/main.c +++ b/test/bacnet/basic/server/bacnet_device/src/main.c @@ -464,6 +464,38 @@ static void testDevice(void) zassert_true(property_list.Required.count > 0, NULL); } } +/** + * @brief Test Device_Last_Restart_Reason_Set() and Device_Last_Restart_Reason() + */ +static void test_Device_Last_Restart_Reason(void) +{ + BACNET_RESTART_REASON reason; + bool status; + unsigned i; + + Device_Init(NULL); + /* default value after init shall be RESTART_REASON_UNKNOWN */ + reason = Device_Last_Restart_Reason(); + zassert_equal(reason, RESTART_REASON_UNKNOWN, NULL); + + /* loop over every valid value (0..BACNET_RESTART_REASON_MAX-1) */ + for (i = 0; i < BACNET_RESTART_REASON_MAX; i++) { + reason = (BACNET_RESTART_REASON)i; + status = Device_Last_Restart_Reason_Set(reason); + zassert_true(status, "reason=%u rejected", i); + zassert_equal( + Device_Last_Restart_Reason(), reason, "reason=%u mismatch", i); + } + + /* BACNET_RESTART_REASON_MAX (256) is out-of-range and must be rejected; + * the previously stored value shall be unchanged */ + status = Device_Last_Restart_Reason_Set(BACNET_RESTART_REASON_MAX); + zassert_false(status, NULL); + zassert_equal( + Device_Last_Restart_Reason(), + (BACNET_RESTART_REASON)(BACNET_RESTART_REASON_MAX - 1), NULL); +} + /** * @} */ @@ -475,7 +507,8 @@ void test_main(void) { ztest_test_suite( device_tests, ztest_unit_test(testDevice), - ztest_unit_test(test_Device_Data_Sharing)); + ztest_unit_test(test_Device_Data_Sharing), + ztest_unit_test(test_Device_Last_Restart_Reason)); ztest_run_test_suite(device_tests); }