Added Exception_Schedule property to schedule object example. (#709)

This commit is contained in:
Steve Karg
2024-08-01 12:15:57 -05:00
committed by GitHub
parent 4ee3bb6fa2
commit ebfaa5eb2c
3 changed files with 209 additions and 94 deletions
+201 -93
View File
@@ -30,7 +30,11 @@ static const int Schedule_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, PROP_PRIORITY_FOR_WRITING, PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, PROP_PRIORITY_FOR_WRITING,
PROP_STATUS_FLAGS, PROP_RELIABILITY, PROP_OUT_OF_SERVICE, -1 }; 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 }; 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) void Schedule_Init(void)
{ {
unsigned i, j; unsigned i, j, e;
BACNET_DATE start_date = { 0 }, end_date = { 0 };
SCHEDULE_DESCR *psched = &Schedule_Descr[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++) { for (i = 0; i < MAX_SCHEDULES; i++, psched++) {
/* whole year, change as necessary */ psched = &Schedule_Descr[i];
psched->Start_Date.year = 0xFF; datetime_copy_date(&psched->Start_Date, &start_date);
psched->Start_Date.month = 1; datetime_copy_date(&psched->End_Date, &end_date);
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;
for (j = 0; j < 7; j++) { for (j = 0; j < 7; j++) {
psched->Weekly_Schedule[j].TV_Count = 0; 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->obj_prop_ref_cnt = 0; /* no references, add as needed */
psched->Priority_For_Writing = 16; /* lowest priority */ psched->Priority_For_Writing = 16; /* lowest priority */
psched->Out_Of_Service = false; 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) bool Schedule_Valid_Instance(uint32_t object_instance)
{ {
unsigned int index = Schedule_Instance_To_Index(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) unsigned Schedule_Count(void)
{ {
return MAX_SCHEDULES; 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) uint32_t Schedule_Index_To_Instance(unsigned index)
{ {
return 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 Schedule_Instance_To_Index(uint32_t instance)
{ {
unsigned index = MAX_SCHEDULES; unsigned index = MAX_SCHEDULES;
@@ -109,6 +173,12 @@ unsigned Schedule_Instance_To_Index(uint32_t instance)
return index; 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( bool Schedule_Object_Name(
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
{ {
@@ -125,13 +195,11 @@ bool Schedule_Object_Name(
return status; return status;
} }
/* BACnet Testing Observed Incident oi00106 /**
Out of service was not supported by Schedule object * @brief Sets a specificSchedule object out of service
Revealed by BACnet Test Client v1.8.16 ( * @param object_instance - object-instance number of the object
www.bac-test.com/bacnet-test-client-download ) BITS: BIT00032 Any discussions * @param value - true if out of service, and false if not
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 */
void Schedule_Out_Of_Service_Set(uint32_t object_instance, bool value) void Schedule_Out_Of_Service_Set(uint32_t object_instance, bool value)
{ {
unsigned index = 0; 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 Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{ {
int apdu_len = 0; int apdu_len = 0;
unsigned object_index = 0; unsigned object_index = 0;
SCHEDULE_DESCR *CurrentSC; SCHEDULE_DESCR *CurrentSC;
uint8_t *apdu = NULL; uint8_t *apdu = NULL;
uint16_t apdu_max = 0;
BACNET_BIT_STRING bit_string; BACNET_BIT_STRING bit_string;
BACNET_CHARACTER_STRING char_string; BACNET_CHARACTER_STRING char_string;
int i; int i;
@@ -156,15 +300,14 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
(rpdata->application_data_len == 0)) { (rpdata->application_data_len == 0)) {
return 0; return 0;
} }
object_index = Schedule_Instance_To_Index(rpdata->object_instance); object_index = Schedule_Instance_To_Index(rpdata->object_instance);
if (object_index < MAX_SCHEDULES) { if (object_index < MAX_SCHEDULES) {
CurrentSC = &Schedule_Descr[object_index]; CurrentSC = &Schedule_Descr[object_index];
} else { } else {
return BACNET_STATUS_ERROR; return BACNET_STATUS_ERROR;
} }
apdu = rpdata->application_data; apdu = rpdata->application_data;
apdu_max = rpdata->application_data_len;
switch ((int)rpdata->object_property) { switch ((int)rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER: case PROP_OBJECT_IDENTIFIER:
apdu_len = encode_application_object_id( 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); apdu_len = bacapp_encode_data(&apdu[0], &CurrentSC->Present_Value);
break; break;
case PROP_EFFECTIVE_PERIOD: 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 = apdu_len =
encode_application_date(&apdu[0], &CurrentSC->Start_Date); encode_application_date(&apdu[0], &CurrentSC->Start_Date);
apdu_len += apdu_len +=
encode_application_date(&apdu[apdu_len], &CurrentSC->End_Date); encode_application_date(&apdu[apdu_len], &CurrentSC->End_Date);
break; break;
case PROP_WEEKLY_SCHEDULE: case PROP_WEEKLY_SCHEDULE:
if (rpdata->array_index == 0) { /* count, always 7 */ apdu_len = bacnet_array_encode(
apdu_len = encode_application_unsigned(&apdu[0], 7); rpdata->object_instance, rpdata->array_index,
} else if (rpdata->array_index == Schedule_Weekly_Schedule_Encode, 7, apdu, apdu_max);
BACNET_ARRAY_ALL) { /* full array */ if (apdu_len == BACNET_STATUS_ABORT) {
int day; rpdata->error_code =
for (day = 0; day < 7; day++) { ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
apdu_len += encode_opening_tag(&apdu[apdu_len], 0); } else if (apdu_len == BACNET_STATUS_ERROR) {
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 */
rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
apdu_len = BACNET_STATUS_ERROR;
} }
break; 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: case PROP_SCHEDULE_DEFAULT:
apdu_len = apdu_len =
bacapp_encode_data(&apdu[0], &CurrentSC->Schedule_Default); bacapp_encode_data(&apdu[0], &CurrentSC->Schedule_Default);
@@ -253,15 +385,6 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
break; break;
case PROP_OUT_OF_SERVICE: 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 = apdu_len =
encode_application_boolean(&apdu[0], CurrentSC->Out_Of_Service); encode_application_boolean(&apdu[0], CurrentSC->Out_Of_Service);
break; break;
@@ -284,24 +407,11 @@ int Schedule_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) 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; unsigned object_index;
bool status = false; /* return value */ bool status = false; /* return value */
int len; int len;
BACNET_APPLICATION_DATA_VALUE value; 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 */ /* decode the some of the request */
len = bacapp_decode_application_data( len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value); 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) { switch ((int)wp_data->object_property) {
case PROP_OUT_OF_SERVICE: 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( status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
if (status) { if (status) {
@@ -352,6 +453,12 @@ bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
return status; 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 Schedule_In_Effective_Period(SCHEDULE_DESCR *desc, BACNET_DATE *date)
{ {
bool res = false; bool res = false;
@@ -366,6 +473,12 @@ bool Schedule_In_Effective_Period(SCHEDULE_DESCR *desc, BACNET_DATE *date)
return res; 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( void Schedule_Recalculate_PV(
SCHEDULE_DESCR *desc, BACNET_WEEKDAY wday, BACNET_TIME *time) 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 */ /* 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 /* Note to developers: please ping Edward at info@connect-ex.com
fully implement the Schedule Object. In good spirit, they have agreed to for a more complete schedule object implementation. */
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. */
for (i = 0; i < desc->Weekly_Schedule[wday - 1].TV_Count && for (i = 0; i < desc->Weekly_Schedule[wday - 1].TV_Count &&
desc->Present_Value.tag == BACNET_APPLICATION_TAG_NULL; desc->Present_Value.tag == BACNET_APPLICATION_TAG_NULL;
i++) { i++) {
+7
View File
@@ -19,6 +19,7 @@
#include "bacnet/rp.h" #include "bacnet/rp.h"
#include "bacnet/bacdevobjpropref.h" #include "bacnet/bacdevobjpropref.h"
#include "bacnet/bactimevalue.h" #include "bacnet/bactimevalue.h"
#include "bacnet/special_event.h"
#ifndef BACNET_WEEKLY_SCHEDULE_SIZE #ifndef BACNET_WEEKLY_SCHEDULE_SIZE
#define BACNET_WEEKLY_SCHEDULE_SIZE 8 /* maximum number of data points for each day */ #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 */ #define BACNET_SCHEDULE_OBJ_PROP_REF_SIZE 4 /* maximum number of obj prop references */
#endif #endif
#ifndef BACNET_EXCEPTION_SCHEDULE_SIZE
#define BACNET_EXCEPTION_SCHEDULE_SIZE 8 /* maximum number of special events */
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -49,6 +53,9 @@ extern "C" {
BACNET_DATE End_Date; BACNET_DATE End_Date;
/* Properties concerning Present Value */ /* Properties concerning Present Value */
BACNET_OBJ_DAILY_SCHEDULE Weekly_Schedule[7]; 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; BACNET_APPLICATION_DATA_VALUE Schedule_Default;
/* /*
* Caution: This is a converted to BACNET_PRIMITIVE_APPLICATION_DATA_VALUE. * Caution: This is a converted to BACNET_PRIMITIVE_APPLICATION_DATA_VALUE.
+1 -1
View File
@@ -28,7 +28,7 @@ static void testSchedule(void)
unsigned count = 0; unsigned count = 0;
uint32_t object_instance = 0; uint32_t object_instance = 0;
const int skip_fail_property_list[] = { const int skip_fail_property_list[] = {
PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES, -1 -1
}; };
Schedule_Init(); Schedule_Init();