Added basic timer object, internal state machine, and unit tests (#1123)

* Added basic timer object, internal state machine, and unit tests

* Added BACnetTimerStateChangeValue encode, decode, parse, print, and diff with unit tests

* Changed handler of add/remove list element to check if the property is a BACnetLIST

* Added BACnetLIST utility for handling WriteProperty to a list.

* Fixed outlier ReadProperty object handlers to return zero when the RP parameter is NULL.
This commit is contained in:
Steve Karg
2025-11-05 15:11:45 -06:00
committed by GitHub
parent 17259b37f3
commit 4dd13cf199
126 changed files with 5509 additions and 56 deletions
+1 -1
View File
@@ -1032,7 +1032,7 @@ int Binary_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
uint8_t *apdu = NULL;
int apdu_size = 0;
if ((rpdata->application_data == NULL) ||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
@@ -1216,6 +1216,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
struct special_property_list_t property_list;
#endif
if (!rpdata) {
return 0;
}
/* initialize the default return values */
rpdata->error_class = ERROR_CLASS_OBJECT;
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+9
View File
@@ -58,6 +58,7 @@
#include "bacnet/basic/object/osv.h"
#include "bacnet/basic/object/piv.h"
#include "bacnet/basic/object/time_value.h"
#include "bacnet/basic/object/timer.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/lo.h"
#include "bacnet/basic/object/blo.h"
@@ -207,6 +208,14 @@ static object_functions_t My_Object_Table[] = {
NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */,
NULL /* Add_List_Element */, NULL /* Remove_List_Element */,
NULL /* Create */, NULL /* Delete */, NULL /* Timer */ },
{ OBJECT_TIMER, Timer_Init, Timer_Count,
Timer_Index_To_Instance, Timer_Valid_Instance,
Timer_Object_Name, Timer_Read_Property,
Timer_Write_Property, Timer_Property_Lists,
NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */,
NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */,
Timer_Add_List_Element, Timer_Remove_List_Element,
Timer_Create, Timer_Delete, Timer_Task },
{ OBJECT_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count,
Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance,
Integer_Value_Object_Name, Integer_Value_Read_Property,
+12 -4
View File
@@ -803,11 +803,15 @@ int Notification_Class_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
list_element->error_class = ERROR_CLASS_SERVICES;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
/* The specified property is currently not modifiable
by the requester.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
/* An array index is provided but the property is
not a BACnetARRAY of BACnetLIST.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
@@ -963,11 +967,15 @@ int Notification_Class_Remove_List_Element(
return BACNET_STATUS_ABORT;
}
if (list_element->object_property != PROP_RECIPIENT_LIST) {
list_element->error_class = ERROR_CLASS_SERVICES;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
/* The specified property is currently not modifiable
by the requester.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
return BACNET_STATUS_ERROR;
}
if (list_element->array_index != BACNET_ARRAY_ALL) {
/* An array index is provided but the property is
not a BACnetARRAY of BACnetLIST.*/
list_element->error_class = ERROR_CLASS_PROPERTY;
list_element->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
return BACNET_STATUS_ERROR;
File diff suppressed because it is too large Load Diff
+189
View File
@@ -0,0 +1,189 @@
/**
* @file
* @brief API for Timer object type
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2025
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_BASIC_OBJECT_TIMER_H
#define BACNET_BASIC_OBJECT_TIMER_H
#include <stdbool.h>
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/bacerror.h"
#include "bacnet/timer_value.h"
#include "bacnet/wp.h"
#include "bacnet/rp.h"
#include "bacnet/list_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
void Timer_Property_Lists(
const int **pRequired, const int **pOptional, const int **pProprietary);
BACNET_STACK_EXPORT
bool Timer_Valid_Instance(uint32_t object_instance);
BACNET_STACK_EXPORT
unsigned Timer_Count(void);
BACNET_STACK_EXPORT
uint32_t Timer_Index_To_Instance(unsigned index);
BACNET_STACK_EXPORT
unsigned Timer_Instance_To_Index(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Object_Name(
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name);
BACNET_STACK_EXPORT
bool Timer_Name_Set(uint32_t object_instance, const char *new_name);
BACNET_STACK_EXPORT
const char *Timer_Name_ASCII(uint32_t object_instance);
BACNET_STACK_EXPORT
int Timer_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata);
BACNET_STACK_EXPORT
bool Timer_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data);
BACNET_STACK_EXPORT
bool Timer_Description(
uint32_t object_instance, BACNET_CHARACTER_STRING *description);
BACNET_STACK_EXPORT
bool Timer_Description_Set(uint32_t instance, const char *new_name);
BACNET_STACK_EXPORT
const char *Timer_Description_ANSI(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Out_Of_Service(uint32_t instance);
BACNET_STACK_EXPORT
void Timer_Out_Of_Service_Set(uint32_t instance, bool oos_flag);
BACNET_STACK_EXPORT
BACNET_RELIABILITY Timer_Reliability(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Reliability_Set(uint32_t object_instance, BACNET_RELIABILITY value);
BACNET_STACK_EXPORT
uint32_t Timer_Present_Value(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Present_Value_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
BACNET_TIMER_STATE Timer_State(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_State_Set(uint32_t object_instance, BACNET_TIMER_STATE value);
BACNET_STACK_EXPORT
BACNET_TIMER_TRANSITION Timer_Last_State_Change(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Running(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Running_Set(uint32_t object_instance, bool running);
BACNET_STACK_EXPORT
bool Timer_Update_Time(uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
BACNET_STACK_EXPORT
bool Timer_Update_Time_Set(
uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
BACNET_STACK_EXPORT
bool Timer_Expiration_Time(
uint32_t object_instance, BACNET_DATE_TIME *bdatetime);
BACNET_STACK_EXPORT
uint32_t Timer_Initial_Timeout(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Initial_Timeout_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint32_t Timer_Default_Timeout(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Default_Timeout_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint32_t Timer_Min_Pres_Value(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Min_Pres_Value_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint32_t Timer_Max_Pres_Value(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Max_Pres_Value_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint32_t Timer_Resolution(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Resolution_Set(uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint8_t Timer_Priority_For_Writing(uint8_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Priority_For_Writing_Set(uint32_t object_instance, uint8_t value);
BACNET_STACK_EXPORT
unsigned Timer_Reference_List_Member_Capacity(uint32_t object_instance);
BACNET_STACK_EXPORT
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Timer_Reference_List_Member_Element(
uint32_t object_instance, unsigned list_index);
BACNET_STACK_EXPORT
bool Timer_Reference_List_Member_Element_Set(
uint32_t object_instance,
unsigned index,
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember);
BACNET_STACK_EXPORT
bool Timer_Reference_List_Member_Element_Add(
uint32_t object_instance,
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pNewMember);
BACNET_STACK_EXPORT
bool Timer_Reference_List_Member_Element_Remove(
uint32_t object_instance,
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pRemoveMember);
BACNET_STACK_EXPORT
unsigned Timer_Reference_List_Member_Element_Count(uint32_t object_instance);
BACNET_STACK_EXPORT
BACNET_TIMER_STATE_CHANGE_VALUE *Timer_State_Change_Value(
uint32_t object_instance, BACNET_TIMER_TRANSITION transition);
BACNET_STACK_EXPORT
bool Timer_State_Change_Value_Set(
uint32_t object_instance,
BACNET_TIMER_TRANSITION transition,
BACNET_TIMER_STATE_CHANGE_VALUE *value);
BACNET_STACK_EXPORT
void Timer_Task(uint32_t object_instance, uint16_t milliseconds);
BACNET_STACK_EXPORT
void Timer_Write_Property_Internal_Callback_Set(write_property_function cb);
BACNET_STACK_EXPORT
int Timer_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element);
BACNET_STACK_EXPORT
int Timer_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element);
BACNET_STACK_EXPORT
uint32_t Timer_Create(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Timer_Delete(uint32_t object_instance);
BACNET_STACK_EXPORT
void Timer_Cleanup(void);
BACNET_STACK_EXPORT
void Timer_Init(void);
/* API for the program requests
note: return value is 0 for success, non-zero for failure
*/
BACNET_STACK_EXPORT
void *Timer_Context_Get(uint32_t object_instance);
BACNET_STACK_EXPORT
void Timer_Context_Set(uint32_t object_instance, void *context);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+45 -4
View File
@@ -53,6 +53,7 @@
#include "bacnet/basic/object/csv.h"
#include "bacnet/basic/object/iv.h"
#include "bacnet/basic/object/time_value.h"
#include "bacnet/basic/object/timer.h"
#include "bacnet/basic/object/channel.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/object/lo.h"
@@ -129,6 +130,7 @@
defined(CONFIG_BACNET_BASIC_OBJECT_FILE) || \
defined(CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW) || \
defined(CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE) || \
defined(CONFIG_BACNET_BASIC_OBJECT_TIMER) || \
defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM) || \
defined(CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE))
#define CONFIG_BACNET_BASIC_OBJECT_ALL
@@ -158,6 +160,7 @@
#define CONFIG_BACNET_BASIC_OBJECT_FILE
#define CONFIG_BACNET_BASIC_OBJECT_STRUCTURED_VIEW
#define CONFIG_BACNET_BASIC_OBJECT_BITSTRING_VALUE
#define CONFIG_BACNET_BASIC_OBJECT_TIMER
#define CONFIG_BACNET_BASIC_OBJECT_PROGRAM
#define CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE
#endif
@@ -185,6 +188,10 @@
#undef CONFIG_BACNET_BASIC_OBJECT_NETWORK_PORT
#warning "Network Port is configured, but BACnet Protocol Revision < 17"
#endif
#ifdef CONFIG_BACNET_BASIC_OBJECT_TIMER
#undef CONFIG_BACNET_BASIC_OBJECT_TIMER
#warning "Timer is configured, but BACnet Protocol Revision < 17"
#endif
#endif
#if (BACNET_PROTOCOL_REVISION < 24)
@@ -750,6 +757,28 @@ static object_functions_t My_Object_Table[] = {
CharacterString_Value_Delete,
NULL /* Timer */ },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_TIMER)
{ OBJECT_TIMER,
Timer_Init,
Timer_Count,
Timer_Index_To_Instance,
Timer_Valid_Instance,
Timer_Object_Name,
Timer_Read_Property,
Timer_Write_Property,
Timer_Property_Lists,
NULL /* ReadRangeInfo */,
NULL /* Iterator */,
NULL /* Value_Lists */,
NULL /* COV */,
NULL /* COV Clear */,
NULL /* Intrinsic Reporting */,
Timer_Add_List_Element,
Timer_Remove_List_Element,
Timer_Create,
Timer_Delete,
Timer_Task },
#endif
#if defined(CONFIG_BACNET_BASIC_OBJECT_PROGRAM)
{ OBJECT_BITSTRING_VALUE,
Program_Init,
@@ -1783,8 +1812,16 @@ uint32_t Device_Interval_Offset(void)
}
#endif
/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or
BACNET_STATUS_ABORT for abort message */
/**
* ReadProperty handler for this object. For the given ReadProperty
* data, the application_data is loaded or the error flags are set.
*
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
* requested data and space for the reply, or error response.
*
* @return number of APDU bytes in the response, zero if no data, or
* BACNET_STATUS_ERROR on error.
*/
int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
{
int apdu_len = 0; /* return value */
@@ -2007,7 +2044,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
* @param pObject - object table
* @param rpdata [in,out] Structure with the requested Object & Property info
* on entry, and APDU message on return.
* @return The length of the APDU on success, else BACNET_STATUS_ERROR
* @return number of APDU bytes in the response, zero if no data, or
* BACNET_STATUS_ERROR on error.
*/
static int Read_Property_Common(
const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata)
@@ -2019,7 +2057,7 @@ static int Read_Property_Common(
struct special_property_list_t property_list;
#endif
if ((rpdata->application_data == NULL) ||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
@@ -2061,6 +2099,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
int apdu_len = BACNET_STATUS_ERROR;
struct object_functions *pObject = NULL;
if (!rpdata) {
return 0;
}
/* initialize the default return values */
rpdata->error_class = ERROR_CLASS_OBJECT;
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+35 -20
View File
@@ -52,7 +52,7 @@ void handler_add_list_element(
BACNET_LIST_ELEMENT_DATA list_element = { 0 };
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
int len = 0;
int len = 0, err = 0;
bool status = true;
int pdu_len = 0;
int bytes_sent = 0;
@@ -102,7 +102,15 @@ void handler_add_list_element(
status = false;
}
if (status) {
if (Device_Add_List_Element(&list_element) >= 0) {
if (!property_list_bacnet_list_member(
list_element.object_type, list_element.object_property)) {
list_element.error_class = ERROR_CLASS_SERVICES;
list_element.error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
err = BACNET_STATUS_ERROR;
} else {
err = Device_Add_List_Element(&list_element);
}
if (err == BACNET_STATUS_OK) {
len = encode_simple_ack(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_ADD_LIST_ELEMENT);
@@ -153,7 +161,7 @@ void handler_remove_list_element(
BACNET_LIST_ELEMENT_DATA list_element = { 0 };
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
int len = 0;
int len = 0, err = BACNET_STATUS_OK;
bool status = true;
int pdu_len = 0;
int bytes_sent = 0;
@@ -192,28 +200,35 @@ void handler_remove_list_element(
(long)list_element.array_index);
} else {
debug_print("RemoveListElement: Unable to decode request!\n");
/* bad decoding or something we didn't understand - send an abort */
err = BACNET_STATUS_ABORT;
}
/* bad decoding or something we didn't understand - send an abort */
if (len <= 0) {
if (len > 0) {
if (!property_list_bacnet_list_member(
list_element.object_type, list_element.object_property)) {
list_element.error_class = ERROR_CLASS_SERVICES;
list_element.error_code = ERROR_CODE_PROPERTY_IS_NOT_A_LIST;
err = BACNET_STATUS_ERROR;
} else {
err = Device_Remove_List_Element(&list_element);
}
}
if (err == BACNET_STATUS_OK) {
len = encode_simple_ack(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT);
debug_print("RemoveListElement: Sending Simple Ack!\n");
} else if (err == BACNET_STATUS_ABORT) {
len = abort_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
ABORT_REASON_OTHER, true);
debug_print("RemoveListElement: Bad Encoding. Sending Abort!\n");
status = false;
}
if (status) {
if (Device_Remove_List_Element(&list_element) >= 0) {
len = encode_simple_ack(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT);
debug_print("RemoveListElement: Sending Simple Ack!\n");
} else {
len = bacerror_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT,
list_element.error_class, list_element.error_code);
debug_print("RemoveListElement: Sending Error!\n");
}
} else {
len = bacerror_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, list_element.error_class,
list_element.error_code);
debug_print("RemoveListElement: Sending Error!\n");
}
}
/* Send PDU */