From ebfaa5eb2cf4122d3d299110a9b9dd079e634591 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 1 Aug 2024 12:15:57 -0500 Subject: [PATCH] Added Exception_Schedule property to schedule object example. (#709) --- src/bacnet/basic/object/schedule.c | 294 +++++++++++++------ src/bacnet/basic/object/schedule.h | 7 + test/bacnet/basic/object/schedule/src/main.c | 2 +- 3 files changed, 209 insertions(+), 94 deletions(-) diff --git a/src/bacnet/basic/object/schedule.c b/src/bacnet/basic/object/schedule.c index aec49801..cc1d3dcb 100644 --- a/src/bacnet/basic/object/schedule.c +++ b/src/bacnet/basic/object/schedule.c @@ -30,7 +30,11 @@ static const int Schedule_Properties_Required[] = { PROP_OBJECT_IDENTIFIER, PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, PROP_PRIORITY_FOR_WRITING, PROP_STATUS_FLAGS, PROP_RELIABILITY, PROP_OUT_OF_SERVICE, -1 }; -static const int Schedule_Properties_Optional[] = { PROP_WEEKLY_SCHEDULE, -1 }; +static const int Schedule_Properties_Optional[] = { PROP_WEEKLY_SCHEDULE, +#if BACNET_EXCEPTION_SCHEDULE_SIZE + PROP_EXCEPTION_SCHEDULE, +#endif + -1 }; static const int Schedule_Properties_Proprietary[] = { -1 }; @@ -48,22 +52,45 @@ void Schedule_Property_Lists( } } +/** + * @brief Gets an object from the list using an instance number + * @param object_instance - object-instance number of the object + * @return object found in the list, or NULL if not found + */ +static SCHEDULE_DESCR *Schedule_Object(uint32_t object_instance) +{ + unsigned int object_index; + SCHEDULE_DESCR *pObject = NULL; + + object_index = Schedule_Instance_To_Index(object_instance); + if (object_index < MAX_SCHEDULES) { + pObject = &Schedule_Descr[object_index]; + } + + return pObject; +} + +/** + * @brief Initialize the Schedule object data + */ void Schedule_Init(void) { - unsigned i, j; - - SCHEDULE_DESCR *psched = &Schedule_Descr[0]; + unsigned i, j, e; + BACNET_DATE start_date = { 0 }, end_date = { 0 }; + BACNET_SPECIAL_EVENT *event; + SCHEDULE_DESCR *psched; + /* whole year, change as necessary */ + datetime_set_date(&start_date, 0, 1, 1); + datetime_wildcard_year_set(&start_date); + datetime_wildcard_weekday_set(&start_date); + datetime_set_date(&end_date, 0, 12, 31); + datetime_wildcard_year_set(&end_date); + datetime_wildcard_weekday_set(&end_date); for (i = 0; i < MAX_SCHEDULES; i++, psched++) { - /* whole year, change as necessary */ - psched->Start_Date.year = 0xFF; - psched->Start_Date.month = 1; - psched->Start_Date.day = 1; - psched->Start_Date.wday = 0xFF; - psched->End_Date.year = 0xFF; - psched->End_Date.month = 12; - psched->End_Date.day = 31; - psched->End_Date.wday = 0xFF; + psched = &Schedule_Descr[i]; + datetime_copy_date(&psched->Start_Date, &start_date); + datetime_copy_date(&psched->End_Date, &end_date); for (j = 0; j < 7; j++) { psched->Weekly_Schedule[j].TV_Count = 0; } @@ -75,9 +102,30 @@ void Schedule_Init(void) psched->obj_prop_ref_cnt = 0; /* no references, add as needed */ psched->Priority_For_Writing = 16; /* lowest priority */ psched->Out_Of_Service = false; +#if BACNET_EXCEPTION_SCHEDULE_SIZE + for (e = 0; e < BACNET_EXCEPTION_SCHEDULE_SIZE; e++) { + event = &psched->Exception_Schedule[e]; + event->periodTag = BACNET_SPECIAL_EVENT_PERIOD_CALENDAR_ENTRY; + event->period.calendarEntry.tag = BACNET_CALENDAR_DATE_RANGE; + datetime_copy_date( + &event->period.calendarEntry.type.DateRange.startdate, + &start_date); + datetime_copy_date( + &event->period.calendarEntry.type.DateRange.enddate, + &end_date); + event->period.calendarEntry.next = NULL; + event->timeValues.TV_Count = 0; + event->priority = 16; + } +#endif } } +/** + * @brief Determines if a given instance is valid + * @param object_instance - object-instance number of the object + * @return true if the instance is valid, and false if not + */ bool Schedule_Valid_Instance(uint32_t object_instance) { unsigned int index = Schedule_Instance_To_Index(object_instance); @@ -88,16 +136,32 @@ bool Schedule_Valid_Instance(uint32_t object_instance) } } +/** + * @brief Determines the number of Schedule objects + * @return Number of Schedule objects + */ unsigned Schedule_Count(void) { return MAX_SCHEDULES; } +/** + * @brief Determines the object instance number for a given index + * @param index - index number of the object + * @return object instance number for the given index, or MAX_SCHEDULES if the + * index is not valid + */ uint32_t Schedule_Index_To_Instance(unsigned index) { return index; } +/** + * @brief Determines the index for a given object instance number + * @param instance - object-instance number of the object + * @return index number for the given object instance number, or MAX_SCHEDULES + * if the instance is not valid + */ unsigned Schedule_Instance_To_Index(uint32_t instance) { unsigned index = MAX_SCHEDULES; @@ -109,6 +173,12 @@ unsigned Schedule_Instance_To_Index(uint32_t instance) return index; } +/** + * @brief Determines the object name for a given object instance number + * @param object_instance - object-instance number of the object + * @param object_name - object name of the object + * @return true if the object name is valid, and false if not + */ bool Schedule_Object_Name( uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) { @@ -125,13 +195,11 @@ bool Schedule_Object_Name( return status; } -/* BACnet Testing Observed Incident oi00106 - Out of service was not supported by Schedule object - Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BITS: BIT00032 Any discussions - can be directed to edward@bac-test.com Please feel free to remove this - comment when my changes accepted after suitable time for review by all - interested parties. Say 6 months -> September 2016 */ +/** + * @brief Sets a specificSchedule object out of service + * @param object_instance - object-instance number of the object + * @param value - true if out of service, and false if not + */ void Schedule_Out_Of_Service_Set(uint32_t object_instance, bool value) { unsigned index = 0; @@ -142,12 +210,88 @@ void Schedule_Out_Of_Service_Set(uint32_t object_instance, bool value) } } +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +static int Schedule_Weekly_Schedule_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = 0, len = 0; + SCHEDULE_DESCR *pObject; + int day, i; + + if (array_index >= 7) { + return BACNET_STATUS_ERROR; + } + pObject = Schedule_Object(object_instance); + if (!pObject) { + return BACNET_STATUS_ERROR; + } + day = array_index; + len = encode_opening_tag(apdu, 0); + apdu_len += len; + if (apdu) { + apdu += len; + } + for (i = 0; i < pObject->Weekly_Schedule[day].TV_Count; i++) { + len = bacnet_time_value_encode(apdu, + &pObject->Weekly_Schedule[day].Time_Values[i]); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + len = encode_closing_tag(apdu, 0); + apdu_len += len; + + return apdu_len; +} + +#if BACNET_EXCEPTION_SCHEDULE_SIZE +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +static int Schedule_Exception_Schedule_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len; + SCHEDULE_DESCR *pObject; + + if (array_index >= BACNET_EXCEPTION_SCHEDULE_SIZE) { + return BACNET_STATUS_ERROR; + } + pObject = Schedule_Object(object_instance); + if (!pObject) { + return BACNET_STATUS_ERROR; + } + apdu_len = bacnet_special_event_encode(apdu, + &pObject->Exception_Schedule[array_index]); + + return apdu_len; +} +#endif + int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) { int apdu_len = 0; unsigned object_index = 0; SCHEDULE_DESCR *CurrentSC; uint8_t *apdu = NULL; + uint16_t apdu_max = 0; BACNET_BIT_STRING bit_string; BACNET_CHARACTER_STRING char_string; int i; @@ -156,15 +300,14 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) (rpdata->application_data_len == 0)) { return 0; } - object_index = Schedule_Instance_To_Index(rpdata->object_instance); if (object_index < MAX_SCHEDULES) { CurrentSC = &Schedule_Descr[object_index]; } else { return BACNET_STATUS_ERROR; } - 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( @@ -182,49 +325,38 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = bacapp_encode_data(&apdu[0], &CurrentSC->Present_Value); break; case PROP_EFFECTIVE_PERIOD: - /* BACnet Testing Observed Incident oi00110 - Effective Period of Schedule object not correctly formatted - Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BITS: BIT00031 Any - discussions can be directed to edward@bac-test.com Please feel - free to remove this comment when my changes accepted after - suitable time for - review by all interested parties. Say 6 months -> September - 2016 */ apdu_len = encode_application_date(&apdu[0], &CurrentSC->Start_Date); apdu_len += encode_application_date(&apdu[apdu_len], &CurrentSC->End_Date); break; case PROP_WEEKLY_SCHEDULE: - if (rpdata->array_index == 0) { /* count, always 7 */ - apdu_len = encode_application_unsigned(&apdu[0], 7); - } else if (rpdata->array_index == - BACNET_ARRAY_ALL) { /* full array */ - int day; - for (day = 0; day < 7; day++) { - apdu_len += encode_opening_tag(&apdu[apdu_len], 0); - for (i = 0; i < CurrentSC->Weekly_Schedule[day].TV_Count; - i++) { - apdu_len += bacnet_time_value_encode(&apdu[apdu_len], - &CurrentSC->Weekly_Schedule[day].Time_Values[i]); - } - apdu_len += encode_closing_tag(&apdu[apdu_len], 0); - } - } else if (rpdata->array_index <= 7) { /* some array element */ - int day = rpdata->array_index - 1; - apdu_len += encode_opening_tag(&apdu[apdu_len], 0); - for (i = 0; i < CurrentSC->Weekly_Schedule[day].TV_Count; i++) { - apdu_len += bacnet_time_value_encode(&apdu[apdu_len], - &CurrentSC->Weekly_Schedule[day].Time_Values[i]); - } - apdu_len += encode_closing_tag(&apdu[apdu_len], 0); - } else { /* out of bounds */ + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Schedule_Weekly_Schedule_Encode, 7, apdu, apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = BACNET_STATUS_ERROR; } break; +#if BACNET_EXCEPTION_SCHEDULE_SIZE + case PROP_EXCEPTION_SCHEDULE: + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Schedule_Exception_Schedule_Encode, + BACNET_EXCEPTION_SCHEDULE_SIZE, apdu, apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + break; +#endif case PROP_SCHEDULE_DEFAULT: apdu_len = bacapp_encode_data(&apdu[0], &CurrentSC->Schedule_Default); @@ -253,15 +385,6 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; case PROP_OUT_OF_SERVICE: - /* BACnet Testing Observed Incident oi00106 - Out of service was not supported by Schedule object - Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BITS: BIT00032 Any - discussions can be directed to edward@bac-test.com Please feel - free to remove this comment when my changes accepted after - suitable time for - review by all interested parties. Say 6 months -> September - 2016 */ apdu_len = encode_application_boolean(&apdu[0], CurrentSC->Out_Of_Service); break; @@ -284,24 +407,11 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) { - /* Ed->Steve, I know that initializing stack values used to be 'safer', but - warnings in latest compilers indicate when uninitialized values are being - used, and I think that the warnings are more useful to reveal bad code - flow than the "safety: of pre-intializing variables. Please give this - some thought let me know if you agree we should start to remove - initializations */ unsigned object_index; bool status = false; /* return value */ int len; BACNET_APPLICATION_DATA_VALUE value; - /* BACnet Testing Observed Incident oi00106 - Out of service was not supported by Schedule object - Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BITS: BIT00032 Any - discussions can be directed to edward@bac-test.com Please feel free to - remove this comment when my changes accepted after suitable time for - review by all interested parties. Say 6 months -> September 2016 */ /* decode the some of the request */ len = bacapp_decode_application_data( wp_data->application_data, wp_data->application_data_len, &value); @@ -320,15 +430,6 @@ bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) switch ((int)wp_data->object_property) { case PROP_OUT_OF_SERVICE: - /* BACnet Testing Observed Incident oi00106 - Out of service was not supported by Schedule object - Revealed by BACnet Test Client v1.8.16 ( - www.bac-test.com/bacnet-test-client-download ) BITS: BIT00032 Any - discussions can be directed to edward@bac-test.com Please feel - free to remove this comment when my changes accepted after - suitable time for - review by all interested parties. Say 6 months -> September - 2016 */ status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); if (status) { @@ -352,6 +453,12 @@ bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) return status; } +/** + * @brief Determine if the given calendar entry is within the effective period + * @param desc - schedule descriptor + * @param date - date to check + * @return true if the calendar entry is within the effective period + */ bool Schedule_In_Effective_Period(SCHEDULE_DESCR *desc, BACNET_DATE *date) { bool res = false; @@ -366,6 +473,12 @@ bool Schedule_In_Effective_Period(SCHEDULE_DESCR *desc, BACNET_DATE *date) return res; } +/** + * @brief Recalculate the Present Value of the Schedule object + * @param desc - schedule descriptor + * @param wday - day of the week + * @param time - time of the day + */ void Schedule_Recalculate_PV( SCHEDULE_DESCR *desc, BACNET_WEEKDAY wday, BACNET_TIME *time) { @@ -374,13 +487,8 @@ void Schedule_Recalculate_PV( /* for future development, here should be the loop for Exception Schedule */ - /* Just a note to developers: We have a paying customer who has asked us to - fully implement the Schedule Object. In good spirit, they have agreed to - allow us to release the code we develop back to the Open Source community - after a 6-12 month waiting period. However, if you are about to work on - this yourself, please ping us at info@connect-ex.com, we may be able to - broker an early release on a case-by-case basis. */ - + /* Note to developers: please ping Edward at info@connect-ex.com + for a more complete schedule object implementation. */ for (i = 0; i < desc->Weekly_Schedule[wday - 1].TV_Count && desc->Present_Value.tag == BACNET_APPLICATION_TAG_NULL; i++) { diff --git a/src/bacnet/basic/object/schedule.h b/src/bacnet/basic/object/schedule.h index 1320a05f..a4c9667d 100644 --- a/src/bacnet/basic/object/schedule.h +++ b/src/bacnet/basic/object/schedule.h @@ -19,6 +19,7 @@ #include "bacnet/rp.h" #include "bacnet/bacdevobjpropref.h" #include "bacnet/bactimevalue.h" +#include "bacnet/special_event.h" #ifndef BACNET_WEEKLY_SCHEDULE_SIZE #define BACNET_WEEKLY_SCHEDULE_SIZE 8 /* maximum number of data points for each day */ @@ -28,6 +29,9 @@ #define BACNET_SCHEDULE_OBJ_PROP_REF_SIZE 4 /* maximum number of obj prop references */ #endif +#ifndef BACNET_EXCEPTION_SCHEDULE_SIZE +#define BACNET_EXCEPTION_SCHEDULE_SIZE 8 /* maximum number of special events */ +#endif #ifdef __cplusplus extern "C" { @@ -49,6 +53,9 @@ extern "C" { BACNET_DATE End_Date; /* Properties concerning Present Value */ BACNET_OBJ_DAILY_SCHEDULE Weekly_Schedule[7]; +#if BACNET_EXCEPTION_SCHEDULE_SIZE + BACNET_SPECIAL_EVENT Exception_Schedule[BACNET_EXCEPTION_SCHEDULE_SIZE]; +#endif BACNET_APPLICATION_DATA_VALUE Schedule_Default; /* * Caution: This is a converted to BACNET_PRIMITIVE_APPLICATION_DATA_VALUE. diff --git a/test/bacnet/basic/object/schedule/src/main.c b/test/bacnet/basic/object/schedule/src/main.c index 6872a699..56279204 100644 --- a/test/bacnet/basic/object/schedule/src/main.c +++ b/test/bacnet/basic/object/schedule/src/main.c @@ -28,7 +28,7 @@ static void testSchedule(void) unsigned count = 0; uint32_t object_instance = 0; const int skip_fail_property_list[] = { - PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, -1 + -1 }; Schedule_Init();