From becfdc947d952a20b130e0a4807d4786e3fbb6ea Mon Sep 17 00:00:00 2001 From: skarg Date: Mon, 2 Apr 2007 21:49:21 +0000 Subject: [PATCH] Adding more functionality to Lighting Output object. --- bacnet-stack/bacenum.h | 54 +- bacnet-stack/demo/object/lo.c | 1139 +++++++++-------- .../ports/pic18f6720/BACnet-Server.mcw | Bin 54272 -> 54272 bytes 3 files changed, 653 insertions(+), 540 deletions(-) diff --git a/bacnet-stack/bacenum.h b/bacnet-stack/bacenum.h index 11b38cc3..43df30ee 100644 --- a/bacnet-stack/bacenum.h +++ b/bacnet-stack/bacenum.h @@ -168,7 +168,7 @@ typedef enum { PROP_EVENT_TIME_STAMPS = 130, PROP_LOG_BUFFER = 131, PROP_LOG_DEVICE_OBJECT = 132, - /* The enable property is renamed from log-enable in + /* The enable property is renamed from log-enable in Addendum b to ANSI/ASHRAE 135-2004(135b-2) */ PROP_ENABLE = 133, PROP_LOG_INTERVAL = 134, @@ -538,10 +538,10 @@ typedef enum { UNITS_SQUARE_METERS_PER_NEWTON = 185, UNITS_WATTS_PER_METER_PER_DEGREE_KELVIN = 189, UNITS_WATTS_PER_SQUARE_METER_DEGREE_KELVIN = 141 - /* Enumerated values 0-255 are reserved for definition by ASHRAE. - Enumerated values 256-65535 may be used by others subject to - the procedures and constraints described in Clause 23. - The last enumeration used in this version is 189. */ + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* The last enumeration used in this version is 189. */ } BACNET_ENGINEERING_UNITS; typedef enum { @@ -573,9 +573,9 @@ typedef enum { PROGRAM_ERROR_INTERNAL = 2, PROGRAM_ERROR_PROGRAM = 3, PROGRAM_ERROR_OTHER = 4 - /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ - /* Enumerated values 64-65535 may be used by others subject to */ - /* the procedures and constraints described in Clause 23. */ + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ } BACNET_PROGRAM_ERROR; typedef enum { @@ -589,10 +589,12 @@ typedef enum { RELIABILITY_UNRELIABLE_OTHER = 7, RELIABILITY_PROCESS_ERROR = 8, RELIABILITY_MULTI_STATE_FAULT = 9, - RELIABILITY_CONFIGURATION_ERROR = 10 - /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ - /* Enumerated values 64-65535 may be used by others subject to */ - /* the procedures and constraints described in Clause 23. */ + RELIABILITY_CONFIGURATION_ERROR = 10, + RELIABILITY_COMMUNICATION_FAILURE = 12, + RELIABILITY_TRIPPED = 13 + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ } BACNET_RELIABILITY; typedef enum { @@ -608,13 +610,13 @@ typedef enum { EVENT_EXTENDED = 9, EVENT_BUFFER_READY = 10, EVENT_UNSIGNED_RANGE = 11 - /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ - /* Enumerated values 64-65535 may be used by others subject to */ - /* the procedures and constraints described in Clause 23. */ - /* It is expected that these enumerated values will correspond to */ - /* the use of the complex-event-type CHOICE [6] of the */ - /* BACnetNotificationParameters production. */ - /* The last enumeration used in this version is 11. */ + /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ + /* Enumerated values 64-65535 may be used by others subject to */ + /* the procedures and constraints described in Clause 23. */ + /* It is expected that these enumerated values will correspond to */ + /* the use of the complex-event-type CHOICE [6] of the */ + /* BACnetNotificationParameters production. */ + /* The last enumeration used in this version is 11. */ } BACNET_EVENT_TYPE; typedef enum { @@ -641,9 +643,9 @@ typedef enum { LIFE_SAFETY_MODE_AUTOMATIC_RELEASE_DISABLED = 13, LIFE_SAFETY_MODE_DEFAULT = 14, MAX_LIFE_SAFETY_MODE = 14 - /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ - /* Enumerated values 256-65535 may be used by others subject to */ - /* procedures and constraints described in Clause 23. */ + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ } BACNET_LIFE_SAFETY_MODE; typedef enum { @@ -689,9 +691,9 @@ typedef enum { LIFE_SAFETY_STATE_SUPERVISORY = 22, LIFE_SAFETY_STATE_TEST_SUPERVISORY = 23, MAX_LIFE_SAFETY_STATE = 0 - /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ - /* Enumerated values 256-65535 may be used by others subject to */ - /* procedures and constraints described in Clause 23. */ + /* Enumerated values 0-255 are reserved for definition by ASHRAE. */ + /* Enumerated values 256-65535 may be used by others subject to */ + /* procedures and constraints described in Clause 23. */ } BACNET_LIFE_SAFETY_STATE; typedef enum { @@ -751,7 +753,7 @@ typedef enum { OBJECT_TREND_LOG_MULTIPLE = 27, OBJECT_LOAD_CONTROL = 28, OBJECT_STRUCTURED_VIEW = 29, - /* what is object type 30? */ + /* what is object type 30? */ OBJECT_LIGHTING_OUTPUT = 31, /* Enumerated values 0-127 are reserved for definition by ASHRAE. */ /* Enumerated values 128-1023 may be used by others subject to */ diff --git a/bacnet-stack/demo/object/lo.c b/bacnet-stack/demo/object/lo.c index 3446533c..b8d7d62d 100644 --- a/bacnet-stack/demo/object/lo.c +++ b/bacnet-stack/demo/object/lo.c @@ -1,514 +1,625 @@ -/************************************************************************** -* -* Copyright (C) 2007 Steve Karg -* -* Permission is hereby granted, free of charge, to any person obtaining -* a copy of this software and associated documentation files (the -* "Software"), to deal in the Software without restriction, including -* without limitation the rights to use, copy, modify, merge, publish, -* distribute, sublicense, and/or sell copies of the Software, and to -* permit persons to whom the Software is furnished to do so, subject to -* the following conditions: -* -* The above copyright notice and this permission notice shall be included -* in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -* -*********************************************************************/ - -/* Lighting Output Objects - customize for your use */ - -#include -#include -#include -#include "bacdef.h" -#include "bacdcode.h" -#include "bacenum.h" -#include "bacapp.h" -#include "config.h" /* the custom stuff */ -#include "wp.h" - -#define MAX_LIGHTING_OUTPUTS 5 - -/* we choose to have a NULL level in our system represented by */ -/* a particular value. When the priorities are not in use, they */ -/* will be relinquished (i.e. set to the NULL level). */ -#define LIGHTING_LEVEL_NULL 255 -/* When all the priorities are level null, the present value returns */ -/* the Relinquish Default value */ -#define LIGHTING_RELINQUISH_DEFAULT 0 - -/* note: although the standard specifies REAL values for some - of the optional parameters, we represent them interally as - integers. */ -typedef struct LightingCommand { - BACNET_LIGHTING_OPERATION operation; - uint8_t level; /* 0..100 percent, 255=not used */ - uint8_t ramp_rate; /* 0..100 percent-per-second, 255=not used */ - uint8_t step_increment; /* 0..100 amount to step, 255=not used */ - uint16_t fade_time; /* 1..65535 seconds to transition, 0=not used */ - uint16_t duration; /* 1..65535 minutes until relinquish, 0=not used */ -} BACNET_LIGHTING_COMMAND; - -/* Here is our Priority Array. They are supposed to be Real, but */ -/* we might not have that kind of memory, so we will use a single byte */ -/* and load a Real for returning the value when asked. */ -static uint8_t - Lighting_Output_Level[MAX_LIGHTING_OUTPUTS][BACNET_MAX_PRIORITY]; -/* The Progress_Value tracks changes such as ramp and fade */ -static uint8_t Lighting_Output_Progress[MAX_LIGHTING_OUTPUTS]; -/* The minimum and maximum present values are used for clamping */ -static uint8_t Lighting_Output_Min_Present_Value[MAX_LIGHTING_OUTPUTS]; -static uint8_t Lighting_Output_Max_Present_Value[MAX_LIGHTING_OUTPUTS]; -/* Writable out-of-service allows others to play with our Present Value */ -/* without changing the physical output */ -static bool Lighting_Output_Out_Of_Service[MAX_LIGHTING_OUTPUTS]; -/* the lighting command is what we are doing */ -static uint8_t Lighting_Command_Priority = 16; -static BACNET_LIGHTING_COMMAND Lighting_Command[MAX_LIGHTING_OUTPUTS]; -/* we need to have our arrays initialized before answering any calls */ -static bool Lighting_Output_Initialized = false; - -void Lighting_Output_Init(void) -{ - unsigned i, j; - - if (!Lighting_Output_Initialized) { - Lighting_Output_Initialized = true; - - /* initialize all the analog output priority arrays to NULL */ - for (i = 0; i < MAX_LIGHTING_OUTPUTS; i++) { - for (j = 0; j < BACNET_MAX_PRIORITY; j++) { - Lighting_Output_Level[i][j] = LIGHTING_LEVEL_NULL; - } - Lighting_Command[i].operation = BACNET_LIGHTS_STOP; - Lighting_Output_Out_Of_Service[i] = false; - Lighting_Output_Progress[i] = LIGHTING_RELINQUISH_DEFAULT; - Lighting_Output_Min_Present_Value[i] = 0; - Lighting_Output_Max_Present_Value[i] = 100; - } - } - - return; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need validate that the */ -/* given instance exists */ -bool Lighting_Output_Valid_Instance(uint32_t object_instance) -{ - Lighting_Output_Init(); - if (object_instance < MAX_LIGHTING_OUTPUTS) - return true; - - return false; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then count how many you have */ -unsigned Lighting_Output_Count(void) -{ - Lighting_Output_Init(); - return MAX_LIGHTING_OUTPUTS; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the instance */ -/* that correlates to the correct index */ -uint32_t Lighting_Output_Index_To_Instance(unsigned index) -{ - Lighting_Output_Init(); - return index; -} - -/* we simply have 0-n object instances. Yours might be */ -/* more complex, and then you need to return the index */ -/* that correlates to the correct instance number */ -unsigned Lighting_Output_Instance_To_Index(uint32_t object_instance) -{ - unsigned index = MAX_LIGHTING_OUTPUTS; - - Lighting_Output_Init(); - if (object_instance < MAX_LIGHTING_OUTPUTS) - index = object_instance; - - return index; -} - -float Lighting_Output_Present_Value(uint32_t object_instance) -{ - float value = LIGHTING_RELINQUISH_DEFAULT; - unsigned index = 0; - unsigned i = 0; - - Lighting_Output_Init(); - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - for (i = 0; i < BACNET_MAX_PRIORITY; i++) { - if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { - value = Lighting_Output_Level[index][i]; - break; - } - } - } - - return value; -} - -unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) -{ - unsigned index = 0; /* instance to index conversion */ - unsigned i = 0; /* loop counter */ - unsigned priority = 0; /* return value */ - - Lighting_Output_Init(); - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - for (i = 0; i < BACNET_MAX_PRIORITY; i++) { - if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { - priority = i + 1; - break; - } - } - } - - return priority; -} - -bool Lighting_Output_Present_Value_Set(uint32_t object_instance, - float value, unsigned priority) -{ - unsigned index = 0; - bool status = false; - - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - if (priority && (priority <= BACNET_MAX_PRIORITY) && - (priority != 6 /* reserved */ ) && - (value >= 0.0) && (value <= 100.0)) { - Lighting_Output_Level[index][priority-1] = (uint8_t) value; - /* Note: you could set the physical output here to the next - highest priority, or to the relinquish default if no - priorities are set. - However, if Out of Service is TRUE, then don't set the - physical output. This comment may apply to the - main loop (i.e. check out of service before changing output) */ - status = true; - } - } - - return status; -} - -bool Lighting_Output_Present_Value_Relinquish(uint32_t object_instance, - int priority) -{ - unsigned index = 0; - bool status = false; - - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - if (priority && (priority <= BACNET_MAX_PRIORITY) && - (priority != 6 /* reserved */ )) { - Lighting_Output_Level[index][priority-1] = LIGHTING_LEVEL_NULL; - /* Note: you could set the physical output here to the next - highest priority, or to the relinquish default if no - priorities are set. - However, if Out of Service is TRUE, then don't set the - physical output. This comment may apply to the - main loop (i.e. check out of service before changing output) */ - status = true; - } - } - - return status; -} - -float Lighting_Output_Progress_Value(uint32_t object_instance) -{ - float value = LIGHTING_RELINQUISH_DEFAULT; - unsigned index = 0; - - Lighting_Output_Init(); - index = Lighting_Output_Instance_To_Index(object_instance); - if (index < MAX_LIGHTING_OUTPUTS) { - value = Lighting_Output_Progress[index]; - } - - return value; -} - -/* note: the object name must be unique within this device */ -char *Lighting_Output_Name(uint32_t object_instance) -{ - static char text_string[32] = ""; /* okay for single thread */ - - if (object_instance < MAX_LIGHTING_OUTPUTS) { - sprintf(text_string, "LIGHTING OUTPUT %u", object_instance); - return text_string; - } - - return NULL; -} - -/* return apdu len, or -1 on error */ -int Lighting_Output_Encode_Property_APDU(uint8_t * apdu, - uint32_t object_instance, - BACNET_PROPERTY_ID property, - int32_t array_index, - BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) -{ - int len = 0; - int apdu_len = 0; /* return value */ - BACNET_BIT_STRING bit_string; - BACNET_CHARACTER_STRING char_string; - float real_value = (float) 1.414; - unsigned object_index = 0; - unsigned i = 0; - bool state = false; - - Lighting_Output_Init(); - switch (property) { - case PROP_OBJECT_IDENTIFIER: - apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_LIGHTING_OUTPUT, - object_instance); - break; - case PROP_OBJECT_NAME: - case PROP_DESCRIPTION: - /* object name must be unique in this device. */ - /* FIXME: description could be writable and different than object name */ - characterstring_init_ansi(&char_string, - Lighting_Output_Name(object_instance)); - apdu_len = encode_tagged_character_string(&apdu[0], &char_string); - break; - case PROP_OBJECT_TYPE: - apdu_len = - encode_tagged_enumerated(&apdu[0], OBJECT_LIGHTING_OUTPUT); - break; - case PROP_PRESENT_VALUE: - real_value = Lighting_Output_Present_Value(object_instance); - apdu_len = encode_tagged_real(&apdu[0], real_value); - break; - case PROP_PROGRESS_VALUE: - real_value = Lighting_Output_Progress_Value(object_instance); - apdu_len = encode_tagged_real(&apdu[0], real_value); - break; - case PROP_STATUS_FLAGS: - bitstring_init(&bit_string); - bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); - bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); - apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); - break; - case PROP_EVENT_STATE: - apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); - break; - case PROP_OUT_OF_SERVICE: - object_index = Lighting_Output_Instance_To_Index(object_instance); - state = Lighting_Output_Out_Of_Service[object_index]; - apdu_len = encode_tagged_boolean(&apdu[0], state); - break; - case PROP_UNITS: - apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); - break; - case PROP_PRIORITY_ARRAY: - /* Array element zero is the number of elements in the array */ - if (array_index == 0) - apdu_len = - encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); - /* if no index was specified, then try to encode the entire list */ - /* into one packet. */ - else if (array_index == BACNET_ARRAY_ALL) { - object_index = - Lighting_Output_Instance_To_Index(object_instance); - for (i = 0; i < BACNET_MAX_PRIORITY; i++) { - /* FIXME: check if we have room before adding it to APDU */ - if (Lighting_Output_Level[object_index][i] == LIGHTING_LEVEL_NULL) - len = encode_tagged_null(&apdu[apdu_len]); - else { - real_value = Lighting_Output_Level[object_index][i]; - len = encode_tagged_real(&apdu[apdu_len], real_value); - } - /* add it if we have room */ - if ((apdu_len + len) < MAX_APDU) - apdu_len += len; - else { - *error_class = ERROR_CLASS_SERVICES; - *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; - apdu_len = -1; - break; - } - } - } else { - object_index = - Lighting_Output_Instance_To_Index(object_instance); - if (array_index <= BACNET_MAX_PRIORITY) { - if (Lighting_Output_Level[object_index][array_index - 1] == - LIGHTING_LEVEL_NULL) - apdu_len = encode_tagged_null(&apdu[0]); - else { - real_value = - Lighting_Output_Level[object_index][array_index - 1]; - apdu_len = encode_tagged_real(&apdu[0], real_value); - } - } else { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; - apdu_len = -1; - } - } - - break; - case PROP_RELINQUISH_DEFAULT: - real_value = LIGHTING_RELINQUISH_DEFAULT; - apdu_len = encode_tagged_real(&apdu[0], real_value); - break; - default: - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_UNKNOWN_PROPERTY; - apdu_len = -1; - break; - } - - return apdu_len; -} - -/* returns true if successful */ -bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, - BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) -{ - bool status = false; /* return value */ - unsigned int object_index = 0; - uint8_t level = LIGHTING_LEVEL_NULL; - int len = 0; - BACNET_APPLICATION_DATA_VALUE value; - - Lighting_Output_Init(); - if (!Lighting_Output_Valid_Instance(wp_data->object_instance)) { - *error_class = ERROR_CLASS_OBJECT; - *error_code = ERROR_CODE_UNKNOWN_OBJECT; - return false; - } - /* decode the some of the request */ - len = bacapp_decode_application_data(wp_data->application_data, - wp_data->application_data_len, &value); - /* FIXME: len < application_data_len: more data? */ - /* FIXME: len == 0: unable to decode? */ - switch (wp_data->object_property) { - case PROP_PRESENT_VALUE: - if (value.tag == BACNET_APPLICATION_TAG_REAL) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - status = - Lighting_Output_Present_Value_Set(wp_data->object_instance, - value.type.Real, wp_data->priority); - if (wp_data->priority == 6) { - /* Command priority 6 is reserved for use by Minimum On/Off - algorithm and may not be used for other purposes in any - object. */ - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else if (!status) { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { - level = LIGHTING_LEVEL_NULL; - object_index = - Lighting_Output_Instance_To_Index(wp_data->object_instance); - status = - Lighting_Output_Present_Value_Relinquish(wp_data-> - object_instance, wp_data->priority); - if (!status) { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } - } else { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_INVALID_DATA_TYPE; - } - break; - case PROP_OUT_OF_SERVICE: - if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { - object_index = - Lighting_Output_Instance_To_Index(wp_data->object_instance); - Lighting_Output_Out_Of_Service[object_index] = - value.type.Boolean; - status = true; - } else { - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_INVALID_DATA_TYPE; - } - break; - default: - *error_class = ERROR_CLASS_PROPERTY; - *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - break; - } - - return status; -} - - -#ifdef TEST -#include -#include -#include "ctest.h" - -void testLightingOutput(Test * pTest) -{ - uint8_t apdu[MAX_APDU] = { 0 }; - int len = 0; - uint32_t len_value = 0; - uint8_t tag_number = 0; - BACNET_OBJECT_TYPE decoded_type = OBJECT_LIGHTING_OUTPUT; - uint32_t decoded_instance = 0; - uint32_t instance = 123; - BACNET_ERROR_CLASS error_class; - BACNET_ERROR_CODE error_code; - - - len = Lighting_Output_Encode_Property_APDU(&apdu[0], - instance, - PROP_OBJECT_IDENTIFIER, - BACNET_ARRAY_ALL, &error_class, &error_code); - ct_test(pTest, len != 0); - len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); - ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); - len = decode_object_id(&apdu[len], - (int *) &decoded_type, &decoded_instance); - ct_test(pTest, decoded_type == OBJECT_LIGHTING_OUTPUT); - ct_test(pTest, decoded_instance == instance); - - return; -} - -#ifdef TEST_LIGHTING_OUTPUT -int main(void) -{ - Test *pTest; - bool rc; - - pTest = ct_create("BACnet Lighting Output", NULL); - /* individual tests */ - rc = ct_addTestFunction(pTest, testLightingOutput); - assert(rc); - - ct_setStream(pTest, stdout); - ct_run(pTest); - (void) ct_report(pTest); - ct_destroy(pTest); - - return 0; -} -#endif /* TEST_LIGHTING_INPUT */ -#endif /* TEST */ +/************************************************************************** +* +* Copyright (C) 2007 Steve Karg +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +* +*********************************************************************/ + +/* Lighting Output Objects - customize for your use */ + +#include +#include +#include +#include "bacdef.h" +#include "bacdcode.h" +#include "bacenum.h" +#include "bacapp.h" +#include "config.h" /* the custom stuff */ +#include "wp.h" + +#define MAX_LIGHTING_OUTPUTS 5 + +/* we choose to have a NULL level in our system represented by */ +/* a particular value. When the priorities are not in use, they */ +/* will be relinquished (i.e. set to the NULL level). */ +#define LIGHTING_LEVEL_NULL 255 +/* When all the priorities are level null, the present value returns */ +/* the Relinquish Default value */ +#define LIGHTING_RELINQUISH_DEFAULT 0 + +/* note: although the standard specifies REAL values for some + of the optional parameters, we represent them interally as + integers. */ +typedef struct LightingCommand { + BACNET_LIGHTING_OPERATION operation; + uint8_t level; /* 0..100 percent, 255=not used */ + uint8_t ramp_rate; /* 0..100 percent-per-second, 255=not used */ + uint8_t step_increment; /* 0..100 amount to step, 255=not used */ + uint16_t fade_time; /* 1..65535 seconds to transition, 0=not used */ + uint16_t duration; /* 1..65535 minutes until relinquish, 0=not used */ +} BACNET_LIGHTING_COMMAND; + +/* Here is our Priority Array. They are supposed to be Real, but */ +/* we might not have that kind of memory, so we will use a single byte */ +/* and load a Real for returning the value when asked. */ +static uint8_t + Lighting_Output_Level[MAX_LIGHTING_OUTPUTS][BACNET_MAX_PRIORITY]; +/* The Progress_Value tracks changes such as ramp and fade */ +static uint8_t Lighting_Output_Progress[MAX_LIGHTING_OUTPUTS]; +/* The minimum and maximum present values are used for clamping */ +static uint8_t Lighting_Output_Min_Present_Value[MAX_LIGHTING_OUTPUTS]; +static uint8_t Lighting_Output_Max_Present_Value[MAX_LIGHTING_OUTPUTS]; +/* Writable out-of-service allows others to play with our Present Value */ +/* without changing the physical output */ +static bool Lighting_Output_Out_Of_Service[MAX_LIGHTING_OUTPUTS]; +/* the lighting command is what we are doing */ +static uint8_t Lighting_Command_Priority = 16; +static BACNET_LIGHTING_COMMAND Lighting_Command[MAX_LIGHTING_OUTPUTS]; +/* we need to have our arrays initialized before answering any calls */ +static bool Lighting_Output_Initialized = false; + +int Lighting_Output_Encode_Lighting_Command(uint8_t * apdu, + BACNET_LIGHTING_COMMAND * data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + int len = 0; /* total length of the apdu, return value */ + float real_value = 0.0; + uint32_t unsigned_value = 0; + + if (apdu) { + len = encode_context_enumerated(&apdu[apdu_len], 0, + data->operation); + apdu_len += len; + /* optional level? */ + if (data->level != 255) { + real_value = data->level; + len = encode_context_real(&apdu[apdu_len], 1, + real_value); + apdu_len += len; + } + /* optional ramp-rate */ + if (data->ramp_rate != 255) { + real_value = data->ramp_rate; + len = encode_context_real(&apdu[apdu_len], 2, + real_value); + apdu_len += len; + } + /* optional step increment */ + if (data->step_increment != 255) { + real_value = data->step_increment; + len = encode_context_real(&apdu[apdu_len], 3, + real_value); + apdu_len += len; + } + /* optional fade time */ + if (data->fade_time != 0) { + real_value = data->fade_time; + len = encode_context_real(&apdu[apdu_len], 4, + real_value); + apdu_len += len; + } + /* optional duration */ + if (data->duration != 0) { + unsigned_value = data->duration; + len = encode_context_unsigned(&apdu[apdu_len], 5, + unsigned_value); + apdu_len += len; + } + } + + return apdu_len; +} + +int Lighting_Output_Decode_Lighting_Command(uint8_t * apdu, + unsigned apdu_max_len, BACNET_LIGHTING_COMMAND * data) +{ + int len = 0; + int apdu_len = 0; + int tag_len = 0; + uint8_t tag_number = 0; + uint32_t len_value_type = 0; + int type = 0; /* for decoding */ + int property = 0; /* for decoding */ + uint32_t unsigned_value = 0; + int i = 0; /* loop counter */ + float real_value = 0.0; + + /* check for value pointers */ + if (apdu_len && data) { + /* Tag 0: operation */ + if (!decode_is_context_tag(&apdu[apdu_len], 0)) + return -1; + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &len_value_type); + apdu_len += len; + len = decode_enumerated(&apdu[apdu_len], len_value_type, &data->operation); + apdu_len += len; + /* Tag 1: level - OPTIONAL */ + if (decode_is_context_tag(&apdu[apdu_len], 1)) { + len = decode_tag_number_and_value(&apdu[apdu_len], + &tag_number, &len_value_type); + apdu_len += len; + len = decode_real(&apdu[apdu_len], &real_value); + apdu_len += len; + data->level = real_value; + /* FIXME: are we going to flag errors in decoding values here? */ + } + /* FIXME: finish me! */ + /* Tag 2: */ + + } + + return len; +} + + +void Lighting_Output_Init(void) +{ + unsigned i, j; + + if (!Lighting_Output_Initialized) { + Lighting_Output_Initialized = true; + + /* initialize all the analog output priority arrays to NULL */ + for (i = 0; i < MAX_LIGHTING_OUTPUTS; i++) { + for (j = 0; j < BACNET_MAX_PRIORITY; j++) { + Lighting_Output_Level[i][j] = LIGHTING_LEVEL_NULL; + } + Lighting_Command[i].operation = BACNET_LIGHTS_STOP; + Lighting_Output_Out_Of_Service[i] = false; + Lighting_Output_Progress[i] = LIGHTING_RELINQUISH_DEFAULT; + Lighting_Output_Min_Present_Value[i] = 0; + Lighting_Output_Max_Present_Value[i] = 100; + } + } + + return; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need validate that the */ +/* given instance exists */ +bool Lighting_Output_Valid_Instance(uint32_t object_instance) +{ + Lighting_Output_Init(); + if (object_instance < MAX_LIGHTING_OUTPUTS) + return true; + + return false; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then count how many you have */ +unsigned Lighting_Output_Count(void) +{ + Lighting_Output_Init(); + return MAX_LIGHTING_OUTPUTS; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the instance */ +/* that correlates to the correct index */ +uint32_t Lighting_Output_Index_To_Instance(unsigned index) +{ + Lighting_Output_Init(); + return index; +} + +/* we simply have 0-n object instances. Yours might be */ +/* more complex, and then you need to return the index */ +/* that correlates to the correct instance number */ +unsigned Lighting_Output_Instance_To_Index(uint32_t object_instance) +{ + unsigned index = MAX_LIGHTING_OUTPUTS; + + Lighting_Output_Init(); + if (object_instance < MAX_LIGHTING_OUTPUTS) + index = object_instance; + + return index; +} + +float Lighting_Output_Present_Value(uint32_t object_instance) +{ + float value = LIGHTING_RELINQUISH_DEFAULT; + unsigned index = 0; + unsigned i = 0; + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { + value = Lighting_Output_Level[index][i]; + break; + } + } + } + + return value; +} + +unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance) +{ + unsigned index = 0; /* instance to index conversion */ + unsigned i = 0; /* loop counter */ + unsigned priority = 0; /* return value */ + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + if (Lighting_Output_Level[index][i] != LIGHTING_LEVEL_NULL) { + priority = i + 1; + break; + } + } + } + + return priority; +} + +bool Lighting_Output_Present_Value_Set(uint32_t object_instance, + float value, unsigned priority) +{ + unsigned index = 0; + bool status = false; + + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ ) && + (value >= 0.0) && (value <= 100.0)) { + Lighting_Output_Level[index][priority-1] = (uint8_t) value; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +bool Lighting_Output_Present_Value_Relinquish(uint32_t object_instance, + int priority) +{ + unsigned index = 0; + bool status = false; + + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */ )) { + Lighting_Output_Level[index][priority-1] = LIGHTING_LEVEL_NULL; + /* Note: you could set the physical output here to the next + highest priority, or to the relinquish default if no + priorities are set. + However, if Out of Service is TRUE, then don't set the + physical output. This comment may apply to the + main loop (i.e. check out of service before changing output) */ + status = true; + } + } + + return status; +} + +float Lighting_Output_Progress_Value(uint32_t object_instance) +{ + float value = LIGHTING_RELINQUISH_DEFAULT; + unsigned index = 0; + + Lighting_Output_Init(); + index = Lighting_Output_Instance_To_Index(object_instance); + if (index < MAX_LIGHTING_OUTPUTS) { + value = Lighting_Output_Progress[index]; + } + + return value; +} + +/* note: the object name must be unique within this device */ +char *Lighting_Output_Name(uint32_t object_instance) +{ + static char text_string[32] = ""; /* okay for single thread */ + + if (object_instance < MAX_LIGHTING_OUTPUTS) { + sprintf(text_string, "LIGHTING OUTPUT %u", object_instance); + return text_string; + } + + return NULL; +} + +/* return apdu len, or -1 on error */ +int Lighting_Output_Encode_Property_APDU(uint8_t * apdu, + uint32_t object_instance, + BACNET_PROPERTY_ID property, + int32_t array_index, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + int len = 0; + int apdu_len = 0; /* return value */ + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + float real_value = (float) 1.414; + unsigned object_index = 0; + unsigned i = 0; + bool state = false; + + Lighting_Output_Init(); + switch (property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_tagged_object_id(&apdu[0], OBJECT_LIGHTING_OUTPUT, + object_instance); + break; + case PROP_OBJECT_NAME: + case PROP_DESCRIPTION: + /* object name must be unique in this device. */ + /* FIXME: description could be writable and different than object name */ + characterstring_init_ansi(&char_string, + Lighting_Output_Name(object_instance)); + apdu_len = encode_tagged_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_tagged_enumerated(&apdu[0], OBJECT_LIGHTING_OUTPUT); + break; + case PROP_PRESENT_VALUE: + real_value = Lighting_Output_Present_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_PROGRESS_VALUE: + real_value = Lighting_Output_Progress_Value(object_instance); + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + case PROP_LIGHTING_COMMAND: + apdu_len = Lighting_Output_Encode_Lighting_Command(&apdu[0], + &Lighting_Command[object_instance]); + break; + case PROP_STATUS_FLAGS: + bitstring_init(&bit_string); + bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false); + apdu_len = encode_tagged_bitstring(&apdu[0], &bit_string); + break; + case PROP_EVENT_STATE: + apdu_len = encode_tagged_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + case PROP_OUT_OF_SERVICE: + object_index = Lighting_Output_Instance_To_Index(object_instance); + state = Lighting_Output_Out_Of_Service[object_index]; + apdu_len = encode_tagged_boolean(&apdu[0], state); + break; + case PROP_UNITS: + apdu_len = encode_tagged_enumerated(&apdu[0], UNITS_PERCENT); + break; + case PROP_PRIORITY_ARRAY: + /* Array element zero is the number of elements in the array */ + if (array_index == 0) + apdu_len = + encode_tagged_unsigned(&apdu[0], BACNET_MAX_PRIORITY); + /* if no index was specified, then try to encode the entire list */ + /* into one packet. */ + else if (array_index == BACNET_ARRAY_ALL) { + object_index = + Lighting_Output_Instance_To_Index(object_instance); + for (i = 0; i < BACNET_MAX_PRIORITY; i++) { + /* FIXME: check if we have room before adding it to APDU */ + if (Lighting_Output_Level[object_index][i] == LIGHTING_LEVEL_NULL) + len = encode_tagged_null(&apdu[apdu_len]); + else { + real_value = Lighting_Output_Level[object_index][i]; + len = encode_tagged_real(&apdu[apdu_len], real_value); + } + /* add it if we have room */ + if ((apdu_len + len) < MAX_APDU) + apdu_len += len; + else { + *error_class = ERROR_CLASS_SERVICES; + *error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT; + apdu_len = -1; + break; + } + } + } else { + object_index = + Lighting_Output_Instance_To_Index(object_instance); + if (array_index <= BACNET_MAX_PRIORITY) { + if (Lighting_Output_Level[object_index][array_index - 1] == + LIGHTING_LEVEL_NULL) + apdu_len = encode_tagged_null(&apdu[0]); + else { + real_value = + Lighting_Output_Level[object_index][array_index - 1]; + apdu_len = encode_tagged_real(&apdu[0], real_value); + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + apdu_len = -1; + } + } + + break; + case PROP_RELINQUISH_DEFAULT: + real_value = LIGHTING_RELINQUISH_DEFAULT; + apdu_len = encode_tagged_real(&apdu[0], real_value); + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = -1; + break; + } + + return apdu_len; +} + +/* returns true if successful */ +bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA * wp_data, + BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code) +{ + bool status = false; /* return value */ + unsigned int object_index = 0; + uint8_t level = LIGHTING_LEVEL_NULL; + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + Lighting_Output_Init(); + if (!Lighting_Output_Valid_Instance(wp_data->object_instance)) { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + return false; + } + /* decode the some of the request */ + len = bacapp_decode_application_data(wp_data->application_data, + wp_data->application_data_len, &value); + /* FIXME: len < application_data_len: more data? */ + /* FIXME: len == 0: unable to decode? */ + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + if (value.tag == BACNET_APPLICATION_TAG_REAL) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + status = + Lighting_Output_Present_Value_Set(wp_data->object_instance, + value.type.Real, wp_data->priority); + if (wp_data->priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else if (value.tag == BACNET_APPLICATION_TAG_NULL) { + level = LIGHTING_LEVEL_NULL; + object_index = + Lighting_Output_Instance_To_Index(wp_data->object_instance); + status = + Lighting_Output_Present_Value_Relinquish(wp_data-> + object_instance, wp_data->priority); + if (wp_data->priority == 6) { + /* Command priority 6 is reserved for use by Minimum On/Off + algorithm and may not be used for other purposes in any + object. */ + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (!status) { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + case PROP_LIGHTING_COMMAND: + /* FIXME: error checking? */ + Lighting_Output_Decode_Lighting_Command(wp_data->application_data, + wp_data->application_data_len, + &Lighting_Command[wp_data->object_instance]); + break; + case PROP_OUT_OF_SERVICE: + if (value.tag == BACNET_APPLICATION_TAG_BOOLEAN) { + object_index = + Lighting_Output_Instance_To_Index(wp_data->object_instance); + Lighting_Output_Out_Of_Service[object_index] = + value.type.Boolean; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + break; + default: + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + } + + return status; +} + + +#ifdef TEST +#include +#include +#include "ctest.h" + +void testLightingOutput(Test * pTest) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0; + uint32_t len_value = 0; + uint8_t tag_number = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_LIGHTING_OUTPUT; + uint32_t decoded_instance = 0; + uint32_t instance = 123; + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; + + + len = Lighting_Output_Encode_Property_APDU(&apdu[0], + instance, + PROP_OBJECT_IDENTIFIER, + BACNET_ARRAY_ALL, &error_class, &error_code); + ct_test(pTest, len != 0); + len = decode_tag_number_and_value(&apdu[0], &tag_number, &len_value); + ct_test(pTest, tag_number == BACNET_APPLICATION_TAG_OBJECT_ID); + len = decode_object_id(&apdu[len], + (int *) &decoded_type, &decoded_instance); + ct_test(pTest, decoded_type == OBJECT_LIGHTING_OUTPUT); + ct_test(pTest, decoded_instance == instance); + + return; +} + +#ifdef TEST_LIGHTING_OUTPUT +int main(void) +{ + Test *pTest; + bool rc; + + pTest = ct_create("BACnet Lighting Output", NULL); + /* individual tests */ + rc = ct_addTestFunction(pTest, testLightingOutput); + assert(rc); + + ct_setStream(pTest, stdout); + ct_run(pTest); + (void) ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif /* TEST_LIGHTING_INPUT */ +#endif /* TEST */ diff --git a/bacnet-stack/ports/pic18f6720/BACnet-Server.mcw b/bacnet-stack/ports/pic18f6720/BACnet-Server.mcw index 2749ec9d81c42cf400af07169da85e0071bde18c..68e526d5e25ed0796466fc15aeeefa4150a3ff1d 100644 GIT binary patch literal 54272 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 literal 54272 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