diff --git a/bacnet-stack/Makefile b/bacnet-stack/Makefile index f6b5628b..4686e153 100644 --- a/bacnet-stack/Makefile +++ b/bacnet-stack/Makefile @@ -15,6 +15,7 @@ MY_BACNET_DEFINES = -DPRINT_ENABLED=1 MY_BACNET_DEFINES += -DBACAPP_ALL MY_BACNET_DEFINES += -DBACFILE MY_BACNET_DEFINES += -DINTRINSIC_REPORTING +MY_BACNET_DEFINES += -DBACNET_TIME_MASTER MY_BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1 BACNET_DEFINES ?= $(MY_BACNET_DEFINES) diff --git a/bacnet-stack/demo/handler/h_ts.c b/bacnet-stack/demo/handler/h_ts.c index 4d636786..06ad77ed 100644 --- a/bacnet-stack/demo/handler/h_ts.c +++ b/bacnet-stack/demo/handler/h_ts.c @@ -26,14 +26,28 @@ #include #include #include "config.h" -#include "txbuf.h" +#include "device.h" +#include "datetime.h" #include "bacdef.h" #include "bacdcode.h" #include "timesync.h" #include "handlers.h" +#include "client.h" +#include "bacaddr.h" /** @file h_ts.c Handles TimeSync requests. */ +#if defined(BACNET_TIME_MASTER) +/* sending time sync to recipients */ +#ifndef MAX_TIME_SYNC_RECIPIENTS +#define MAX_TIME_SYNC_RECIPIENTS 16 +#endif +BACNET_RECIPIENT_LIST Time_Sync_Recipients[MAX_TIME_SYNC_RECIPIENTS]; +/* variable used for controlling when to + automatically send a TimeSynchronization request */ +static BACNET_DATE_TIME Next_Sync_Time; +#endif + #if PRINT_ENABLED static void show_bacnet_date_time( BACNET_DATE * bdate, @@ -58,8 +72,8 @@ void handler_timesync( BACNET_ADDRESS * src) { int len = 0; - BACNET_DATE bdate; - BACNET_TIME btime; + BACNET_DATE bdate = {0}; + BACNET_TIME btime = {0}; (void) src; (void) service_len; @@ -67,12 +81,16 @@ void handler_timesync( timesync_decode_service_request(service_request, service_len, &bdate, &btime); if (len > 0) { + if (datetime_is_valid(&bdate, &btime)) { + /* fixme: only set the time if off by some amount */ #if PRINT_ENABLED - fprintf(stderr, "Received TimeSyncronization Request\r\n"); - show_bacnet_date_time(&bdate, &btime); + fprintf(stderr, "Received TimeSyncronization Request\r\n"); + show_bacnet_date_time(&bdate, &btime); #else - /* FIXME: set the time? */ + /* FIXME: set the time? + Maybe only set the time if off by some amount */ #endif + } } return; @@ -93,12 +111,188 @@ void handler_timesync_utc( timesync_decode_service_request(service_request, service_len, &bdate, &btime); if (len > 0) { + if (datetime_is_valid(&bdate, &btime)) { #if PRINT_ENABLED - fprintf(stderr, "Received TimeSyncronization Request\r\n"); - show_bacnet_date_time(&bdate, &btime); + fprintf(stderr, "Received TimeSyncronization Request\r\n"); + show_bacnet_date_time(&bdate, &btime); #endif - /* FIXME: set the time? */ + /* FIXME: set the time? + only set the time if off by some amount */ + } } return; } + +#if defined(BACNET_TIME_MASTER) +/** Handle a request to list all the timesync recipients. + * + * Invoked by a request to read the Device object's + * PROP_TIME_SYNCHRONIZATION_RECIPIENTS. + * Loops through the list of timesync recipients, and, for each valid one, + * adds its data to the APDU. + * + * @param apdu [out] Buffer in which the APDU contents are built. + * @param max_apdu [in] Max length of the APDU buffer. + * + * @return How many bytes were encoded in the buffer, or + * BACNET_STATUS_ABORT if the response would not fit within the buffer. + */ +int handler_timesync_encode_recipients( + uint8_t * apdu, + int max_apdu) +{ + return timesync_encode_timesync_recipients(apdu, max_apdu, + &Time_Sync_Recipients[0]); +} +#endif + +#if defined(BACNET_TIME_MASTER) +bool handler_timesync_recipient_write( + BACNET_WRITE_PROPERTY_DATA * wp_data) +{ + bool status = false; + + /* fixme: handle writing of the recipient list */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + + return status; +} +#endif + +#if defined(BACNET_TIME_MASTER) +static void handler_timesync_send( + BACNET_DATE_TIME * current_date_time) +{ + unsigned index = 0; + bool status = false; + + for (index = 0; index < MAX_TIME_SYNC_RECIPIENTS; index++) { + if (Time_Sync_Recipients[index].tag == 1) { + if (status) { + Send_TimeSync_Remote( + &Time_Sync_Recipients[index].type.address, + ¤t_date_time->date, + ¤t_date_time->time); + } + } + } +} +#endif + +#if defined(BACNET_TIME_MASTER) +static void handler_timesync_update( + uint32_t device_interval, + BACNET_DATE_TIME * current_date_time) +{ + uint32_t current_minutes = 0; + uint32_t next_minutes = 0; + uint32_t delta_minutes = 0; + uint32_t offset_minutes = 0; + uint32_t interval = 0; + uint32_t interval_offset = 0; + + datetime_copy(&Next_Sync_Time, current_date_time); + if (Device_Align_Intervals()) { + interval_offset = Device_Interval_Offset(); + /* 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 ((60 % device_interval) == 0) { + /* factor of an hour alignment */ + /* Interval_Minutes = 1 2 3 4 5 6 10 12 15 20 30 60 */ + /* determine next interval */ + current_minutes = Next_Sync_Time.time.min; + interval = current_minutes/device_interval; + interval++; + next_minutes = interval * device_interval; + offset_minutes = interval_offset % device_interval; + next_minutes += offset_minutes; + delta_minutes = next_minutes - current_minutes; + datetime_add_minutes(&Next_Sync_Time, delta_minutes); + Next_Sync_Time.time.sec = 0; + Next_Sync_Time.time.hundredths = 0; + } else if ((1440 % device_interval) == 0) { + /* factor of a day alignment */ + /* Interval_Minutes = 1 2 3 4 5 6 8 9 10 12 15 16 + 18 20 24 30 32 36 40 45 48 60 72 80 90 96 120 + 144 160 180 240 288 360 480 720 1440 */ + current_minutes = + datetime_minutes_since_midnight(&Next_Sync_Time.time); + interval = current_minutes/device_interval; + interval++; + next_minutes = interval * device_interval; + offset_minutes = interval_offset % device_interval; + next_minutes += offset_minutes; + delta_minutes = next_minutes - current_minutes; + datetime_add_minutes(&Next_Sync_Time, delta_minutes); + Next_Sync_Time.time.sec = 0; + Next_Sync_Time.time.hundredths = 0; + } + } else { + datetime_add_minutes(&Next_Sync_Time, device_interval); + Next_Sync_Time.time.sec = 0; + Next_Sync_Time.time.hundredths = 0; + } +} +#endif + +#if defined(BACNET_TIME_MASTER) +bool handler_timesync_recipient_address_set( + unsigned index, + BACNET_ADDRESS *address) +{ + bool status = false; + + if (address && (index < MAX_TIME_SYNC_RECIPIENTS)) { + Time_Sync_Recipients[index].tag = 1; + bacnet_address_copy( + &Time_Sync_Recipients[index].type.address, + address); + status = true; + } + + return status; +} +#endif + +#if defined(BACNET_TIME_MASTER) +void handler_timesync_task( + BACNET_DATE_TIME * current_date_time) +{ + int compare = 0; + uint32_t device_interval = 0; + + device_interval = Device_Time_Sync_Interval(); + if (device_interval) { + compare = datetime_compare(current_date_time, &Next_Sync_Time); + /* if the date/times are the same, return is 0 + if date1 is before date2, returns negative + if date1 is after date2, returns positive */ + if (compare >= 0) { + handler_timesync_update(device_interval, current_date_time); + handler_timesync_send(current_date_time); + } + } +} +#endif + +#if defined(BACNET_TIME_MASTER) +void handler_timesync_init(void) +{ + unsigned i = 0; + + /* connect linked list */ + for (; i < (MAX_TIME_SYNC_RECIPIENTS-1); i++) { + Time_Sync_Recipients[i].next = &Time_Sync_Recipients[i+1]; + Time_Sync_Recipients[i+1].next = NULL; + } + for (i = 0; i < MAX_TIME_SYNC_RECIPIENTS; i++) { + Time_Sync_Recipients[i].tag = 0xFF; + } +} +#endif diff --git a/bacnet-stack/demo/object/device-client.c b/bacnet-stack/demo/object/device-client.c index d5918023..10877333 100644 --- a/bacnet-stack/demo/object/device-client.c +++ b/bacnet-stack/demo/object/device-client.c @@ -87,8 +87,13 @@ static BACNET_DATE Local_Date; /* rely on OS, if there is one */ BACnet UTC offset is expressed in minutes. */ static int32_t UTC_Offset = 5 * 60; static bool Daylight_Savings_Status = false; /* rely on OS */ -/* List_Of_Session_Keys */ +#if defined(BACNET_TIME_MASTER) +static bool Align_Intervals; +static uint32_t Interval_Minutes; +static uint32_t Interval_Offset_Minutes; /* Time_Synchronization_Recipients */ +#endif +/* List_Of_Session_Keys */ /* Max_Master - rely on MS/TP subsystem, if there is one */ /* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ /* Device_Address_Binding - required, but relies on binding cache */ @@ -720,6 +725,80 @@ 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( diff --git a/bacnet-stack/demo/object/device.c b/bacnet-stack/demo/object/device.c index 948ae9d6..21caadc2 100644 --- a/bacnet-stack/demo/object/device.c +++ b/bacnet-stack/demo/object/device.c @@ -644,6 +644,12 @@ static const int Device_Properties_Optional[] = { PROP_DAYLIGHT_SAVINGS_STATUS, PROP_LOCATION, PROP_ACTIVE_COV_SUBSCRIPTIONS, +#if defined(BACNET_TIME_MASTER) + PROP_TIME_SYNCHRONIZATION_RECIPIENTS, + PROP_TIME_SYNCHRONIZATION_INTERVAL, + PROP_ALIGN_INTERVALS, + PROP_INTERVAL_OFFSET, +#endif -1 }; @@ -697,8 +703,13 @@ static BACNET_DATE Local_Date; /* rely on OS, if there is one */ BACnet UTC offset is expressed in minutes. */ static int32_t UTC_Offset = 5 * 60; static bool Daylight_Savings_Status = false; /* rely on OS */ -/* List_Of_Session_Keys */ +#if defined(BACNET_TIME_MASTER) +static bool Align_Intervals; +static uint32_t Interval_Minutes; +static uint32_t Interval_Offset_Minutes; /* Time_Synchronization_Recipients */ +#endif +/* List_Of_Session_Keys */ /* Max_Master - rely on MS/TP subsystem, if there is one */ /* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ /* Device_Address_Binding - required, but relies on binding cache */ @@ -1263,11 +1274,90 @@ int32_t Device_UTC_Offset(void) 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( @@ -1481,6 +1571,29 @@ int Device_Read_Property_Local( apdu_len = encode_application_unsigned(&apdu[0], dlmstp_max_master()); break; +#endif +#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_TIME_SYNCHRONIZATION_INTERVAL: + apdu_len = encode_application_unsigned(&apdu[0], + Device_Time_Sync_Interval()); + break; + case PROP_ALIGN_INTERVALS: + apdu_len = + encode_application_boolean(&apdu[0], + Device_Align_Intervals()); + break; + case PROP_INTERVAL_OFFSET: + apdu_len = encode_application_unsigned(&apdu[0], + Device_Interval_Offset()); + break; #endif case PROP_ACTIVE_COV_SUBSCRIPTIONS: apdu_len = handler_cov_encode_subscriptions(&apdu[0], apdu_max); @@ -1558,6 +1671,7 @@ bool Device_Write_Property_Local( BACNET_APPLICATION_DATA_VALUE value; int object_type = 0; uint32_t object_instance = 0; + uint32_t minutes = 0; int temp; /* decode the some of the request */ @@ -1696,9 +1810,71 @@ bool Device_Write_Property_Local( characterstring_length(&value.type.Character_String)); } break; - - case PROP_MAX_INFO_FRAMES: +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + 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; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_ALIGN_INTERVALS: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + Device_Align_Intervals_Set(value.type.Boolean); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_INTERVAL_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + 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; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; +#else + case PROP_TIME_SYNCHRONIZATION_INTERVAL: + case PROP_ALIGN_INTERVALS: + case PROP_INTERVAL_OFFSET: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; +#endif + case PROP_UTC_OFFSET: + if (value.tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + 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: status = WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, &wp_data->error_class, &wp_data->error_code); @@ -1713,9 +1889,7 @@ bool Device_Write_Property_Local( } } break; -#endif case PROP_MAX_MASTER: -#if defined(BACDL_MSTP) status = WPValidateArgType(&value, BACNET_APPLICATION_TAG_UNSIGNED_INT, &wp_data->error_class, &wp_data->error_code); @@ -1730,13 +1904,18 @@ bool Device_Write_Property_Local( } } break; +#else + case PROP_MAX_INFO_FRAMES: + case PROP_MAX_MASTER: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; #endif case PROP_OBJECT_TYPE: case PROP_VENDOR_NAME: case PROP_FIRMWARE_REVISION: case PROP_APPLICATION_SOFTWARE_VERSION: case PROP_LOCAL_TIME: - case PROP_UTC_OFFSET: case PROP_LOCAL_DATE: case PROP_DAYLIGHT_SAVINGS_STATUS: case PROP_PROTOCOL_VERSION: @@ -1749,6 +1928,9 @@ bool Device_Write_Property_Local( case PROP_DEVICE_ADDRESS_BINDING: case PROP_DATABASE_REVISION: case PROP_ACTIVE_COV_SUBSCRIPTIONS: +#if defined(BACNET_TIME_MASTER) + case PROP_TIME_SYNCHRONIZATION_RECIPIENTS: +#endif wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; break; diff --git a/bacnet-stack/demo/object/device.h b/bacnet-stack/demo/object/device.h index 979c3dcd..aeb269bc 100644 --- a/bacnet-stack/demo/object/device.h +++ b/bacnet-stack/demo/object/device.h @@ -237,6 +237,12 @@ extern "C" { BACNET_DATE_TIME * DateTime); int32_t Device_UTC_Offset(void); bool Device_Daylight_Savings_Status(void); + bool Device_Align_Intervals(void); + bool Device_Align_Intervals_Set(bool flag); + uint32_t Device_Time_Sync_Interval(void); + bool Device_Time_Sync_Interval_Set(uint32_t value); + uint32_t Device_Interval_Offset(void); + bool Device_Interval_Offset_Set(uint32_t value); void Device_Property_Lists( const int **pRequired, diff --git a/bacnet-stack/demo/server/main.c b/bacnet-stack/demo/server/main.c index dfa78523..ddd153eb 100644 --- a/bacnet-stack/demo/server/main.c +++ b/bacnet-stack/demo/server/main.c @@ -146,6 +146,9 @@ static void Init_Service_Handlers( apdu_set_confirmed_handler(SERVICE_CONFIRMED_GET_ALARM_SUMMARY, handler_get_alarm_summary); #endif /* defined(INTRINSIC_REPORTING) */ +#if defined(BACNET_TIME_MASTER) + handler_timesync_init(); +#endif } static void print_usage(const char *filename) @@ -196,6 +199,9 @@ int main( uint32_t elapsed_milliseconds = 0; uint32_t address_binding_tmr = 0; uint32_t recipient_scan_tmr = 0; +#if defined(BACNET_TIME_MASTER) + BACNET_DATE_TIME bdatetime; +#endif #if defined(BAC_UCI) int uciId = 0; struct uci_context *ctx; @@ -282,6 +288,10 @@ int main( trend_log_timer(elapsed_seconds); #if defined(INTRINSIC_REPORTING) Device_local_reporting(); +#endif +#if defined(BACNET_TIME_MASTER) + Device_getCurrentDateTime(&bdatetime); + handler_timesync_task(&bdatetime); #endif } handler_cov_task(); diff --git a/bacnet-stack/include/handlers.h b/bacnet-stack/include/handlers.h index ea7a8ec6..095f6638 100644 --- a/bacnet-stack/include/handlers.h +++ b/bacnet-stack/include/handlers.h @@ -195,8 +195,6 @@ extern "C" { void); bool handler_timesync_recipient_write( BACNET_WRITE_PROPERTY_DATA * wp_data); - bool handler_timesync_interval_set( - uint32_t minutes); bool handler_timesync_recipient_address_set( unsigned index, BACNET_ADDRESS * address);