diff --git a/CMakeLists.txt b/CMakeLists.txt index 59c83fb9..3cd1cf0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,6 +188,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/object/bacfile.h src/bacnet/basic/object/bi.c src/bacnet/basic/object/bi.h + src/bacnet/basic/object/blo.c + src/bacnet/basic/object/blo.h src/bacnet/basic/object/bo.c src/bacnet/basic/object/bo.h src/bacnet/basic/object/bv.c diff --git a/apps/blinkt/main.c b/apps/blinkt/main.c index 1fe6ab5f..d3f967f9 100644 --- a/apps/blinkt/main.c +++ b/apps/blinkt/main.c @@ -118,9 +118,6 @@ static void Init_Service_Handlers(void) /* handle communication so we can shutup when asked */ apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, handler_device_communication_control); - /* handle the data coming back from private requests */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, - handler_unconfirmed_private_transfer); /* configure the cyclic timers */ mstimer_set(&BACnet_Task_Timer, 1000UL); mstimer_set(&BACnet_TSM_Timer, 50UL); @@ -297,7 +294,7 @@ static void bacnet_output_init(void) Channel_Reference_List_Member_Element_Set( light_channel_instance, 1 + i, &member); - object_instance++; + object_instance = 1 + i; } Color_Write_Present_Value_Callback_Set(Color_Write_Value_Handler); Color_Temperature_Write_Present_Value_Callback_Set( diff --git a/apps/gateway/Makefile b/apps/gateway/Makefile index 6db86819..56ce1583 100644 --- a/apps/gateway/Makefile +++ b/apps/gateway/Makefile @@ -15,6 +15,7 @@ BACNET_OBJECT_SRC := \ $(BACNET_OBJECT_DIR)/ao.c \ $(BACNET_OBJECT_DIR)/av.c \ $(BACNET_OBJECT_DIR)/bi.c \ + $(BACNET_OBJECT_DIR)/blo.c \ $(BACNET_OBJECT_DIR)/bo.c \ $(BACNET_OBJECT_DIR)/bv.c \ $(BACNET_OBJECT_DIR)/channel.c \ diff --git a/apps/piface/Makefile b/apps/piface/Makefile index c7ef0989..ac29b929 100644 --- a/apps/piface/Makefile +++ b/apps/piface/Makefile @@ -10,6 +10,7 @@ SRC = main.c \ device.c \ $(BACNET_OBJECT_DIR)/netport.c \ $(BACNET_OBJECT_DIR)/bi.c \ + $(BACNET_OBJECT_DIR)/blo.c \ $(BACNET_OBJECT_DIR)/bo.c CFLAGS += -DMAX_TSM_TRANSACTIONS=1 diff --git a/apps/piface/device.c b/apps/piface/device.c index d90db89b..1452be59 100644 --- a/apps/piface/device.c +++ b/apps/piface/device.c @@ -47,6 +47,7 @@ /* include the OS specific */ #include "bacnet/basic/object/device.h" #include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/blo.h" #include "bacnet/basic/object/bo.h" #if (BACNET_PROTOCOL_REVISION >= 17) #include "bacnet/basic/object/netport.h" @@ -88,6 +89,18 @@ static object_functions_t My_Object_Table[] = { Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, NULL /* Remove_List_Element */, NULL /* Create */, NULL /* Delete */, NULL /* Timer */ }, + { OBJECT_BINARY_LIGHTING_OUTPUT, Binary_Lighting_Output_Init, + Binary_Lighting_Output_Count, Binary_Lighting_Output_Index_To_Instance, + Binary_Lighting_Output_Valid_Instance, + Binary_Lighting_Output_Object_Name, + Binary_Lighting_Output_Read_Property, + Binary_Lighting_Output_Write_Property, + Binary_Lighting_Output_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Lighting_Output_Create, Binary_Lighting_Output_Delete, + Binary_Lighting_Output_Timer }, { OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count, Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, Binary_Output_Object_Name, Binary_Output_Read_Property, @@ -1979,3 +1992,30 @@ bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ return status; } + +/** + * @brief Updates all the object timers with elapsed milliseconds + * @param milliseconds - number of milliseconds elapsed + */ +void Device_Timer(uint16_t milliseconds) +{ + struct object_functions *pObject; + unsigned count = 0; + uint32_t instance; + + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Count) { + count = pObject->Object_Count(); + } + while (count) { + count--; + if ((pObject->Object_Timer) && + (pObject->Object_Index_To_Instance)) { + instance = pObject->Object_Index_To_Instance(count); + pObject->Object_Timer(instance, milliseconds); + } + } + pObject++; + } +} diff --git a/apps/piface/main.c b/apps/piface/main.c index 10b00777..66894b93 100644 --- a/apps/piface/main.c +++ b/apps/piface/main.c @@ -1,33 +1,17 @@ -/************************************************************************** +/** + * @file + * @brief + * @author Steve Karg + * @date January 2023 * - * Copyright (C) 2006 Steve Karg + * SPDX-License-Identifier: MIT * - * 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. - * - *********************************************************************/ + */ #include #include #include #include #include -#include #include "bacnet/config.h" #include "bacnet/basic/binding/address.h" @@ -39,40 +23,117 @@ #include "bacnet/npdu.h" #include "bacnet/apdu.h" #include "bacnet/iam.h" -#include "bacnet/basic/tsm/tsm.h" #include "bacnet/basic/object/device.h" #include "bacnet/basic/object/bacfile.h" #include "bacnet/datalink/datalink.h" #include "bacnet/dcc.h" #include "bacnet/getevent.h" #include "bacport.h" -#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/tsm/tsm.h" #include "bacnet/version.h" /* include the device object */ #include "bacnet/basic/object/device.h" #include "bacnet/basic/object/bi.h" +#include "bacnet/basic/object/blo.h" #include "bacnet/basic/object/bo.h" #include "pifacedigital.h" /** @file server/main.c Example server application using the BACnet Stack. */ -/* (Doxygen note: The next two lines pull all the following Javadoc - * into the ServerDemo module.) */ -/** @addtogroup ServerDemo */ -/*@{*/ +/* number of binary outputs on the PiFace card */ +#define PIFACE_OUTPUTS_MAX 8 /** Buffer used for receiving */ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; - /* current version of the BACnet stack */ static const char *BACnet_Version = BACNET_VERSION_TEXT; +/* task timer for various BACnet timeouts */ +static struct mstimer BACnet_Task_Timer; +/* task timer for TSM timeouts */ +static struct mstimer BACnet_TSM_Timer; +/* task timer for address binding timeouts */ +static struct mstimer BACnet_Address_Timer; +/* task timer for object functionality */ +static struct mstimer BACnet_Object_Timer; +/* track the state of of the output */ +static bool PiFace_Output_State[PIFACE_OUTPUTS_MAX]; + +/** + * @brief output write value request + * @param index - 0..N index of the output + * @param value - value of the write ON or OFF + */ +static void piface_write_output(int index, BACNET_BINARY_LIGHTING_PV value) +{ + if (index < PIFACE_OUTPUTS_MAX) { + if (value == BINARY_LIGHTING_PV_OFF) { + pifacedigital_digital_write(index, 0); + PiFace_Output_State[index] = false; + printf("OUTPUT[%u]=OFF\n", index); + } else if (value == BINARY_LIGHTING_PV_ON) { + pifacedigital_digital_write(index, 1); + PiFace_Output_State[index] = true; + printf("OUTPUT[%u]=ON\n", index); + } + } +} + +/** + * @brief Callback for write value request + * @param object_instance - object-instance number of the object + * @param old_value - value prior to write + * @param value - value of the write + */ +static void Binary_Lighting_Output_Write_Value_Handler(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV old_value, + BACNET_BINARY_LIGHTING_PV value) +{ + unsigned index; + + index = Binary_Lighting_Output_Instance_To_Index(object_instance); + if (index < PIFACE_OUTPUTS_MAX) { + printf("BLO-WRITE: OUTPUT[%u]=%d present=%d feedback=%d target=%d\n", + index, (int)value, + (int)Binary_Lighting_Output_Present_Value(object_instance), + (int)old_value, + (int)Binary_Lighting_Output_Lighting_Command_Target_Value( + object_instance)); + piface_write_output(index, value); + } +} + +/** + * @brief Callback for blink warning notification + * @param object_instance - object-instance number of the object + */ +static void Binary_Lighting_Output_Blink_Warn_Handler(uint32_t object_instance) +{ + unsigned index; + + index = Binary_Lighting_Output_Instance_To_Index(object_instance); + if (index < PIFACE_OUTPUTS_MAX) { + /* blink is just toggle on/off every one second */ + if (PiFace_Output_State[index]) { + printf("BLO-BLINK: OUTPUT[%u]=%d\n", index, + BINARY_LIGHTING_PV_OFF); + piface_write_output(index, BINARY_LIGHTING_PV_OFF); + } else { + printf( + "BLO-BLINK: OUTPUT[%u]=%d\n", index, BINARY_LIGHTING_PV_ON); + piface_write_output(index, BINARY_LIGHTING_PV_ON); + } + } +} /** Initialize the handlers we will utilize. * @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler */ static void Init_Service_Handlers(void) { + uint32_t i = 0; + uint32_t object_instance; + Device_Init(NULL); /* we need to handle who-is to support dynamic device binding */ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); @@ -107,9 +168,21 @@ static void Init_Service_Handlers(void) /* handle communication so we can shutup when asked */ apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, handler_device_communication_control); - /* handle the data coming back from private requests */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, - handler_unconfirmed_private_transfer); + /* configure the cyclic timers */ + mstimer_set(&BACnet_Task_Timer, 1000UL); + mstimer_set(&BACnet_TSM_Timer, 50UL); + mstimer_set(&BACnet_Address_Timer, 60UL * 1000UL); + mstimer_set(&BACnet_Object_Timer, 1000UL); + /* create some objects */ + for (i = 0; i < PIFACE_OUTPUTS_MAX; i++) { + object_instance = 1 + i; + Binary_Lighting_Output_Create(object_instance); + Binary_Output_Create(object_instance); + } + Binary_Lighting_Output_Write_Value_Callback_Set( + Binary_Lighting_Output_Write_Value_Handler); + Binary_Lighting_Output_Blink_Warn_Callback_Set( + Binary_Lighting_Output_Blink_Warn_Handler); } static void piface_init(void) @@ -159,6 +232,7 @@ static void piface_task(void) unsigned i = 0; BACNET_BINARY_PV present_value = BINARY_INACTIVE; bool pin_status = false; + uint32_t object_instance; for (i = 0; i < MAX_BINARY_INPUTS; i++) { if (!Binary_Input_Out_Of_Service(i)) { @@ -181,13 +255,24 @@ static void piface_task(void) } } } - for (i = 0; i < MAX_BINARY_OUTPUTS; i++) { - if (!Binary_Output_Out_Of_Service(i)) { - present_value = Binary_Output_Present_Value(i); - if (present_value == BINARY_INACTIVE) { - pifacedigital_digital_write(i, 0); - } else { - pifacedigital_digital_write(i, 1); + for (i = 0; i < PIFACE_OUTPUTS_MAX; i++) { + object_instance = Binary_Output_Index_To_Instance(i); + if (Binary_Output_Valid_Instance(object_instance)) { + if (!Binary_Output_Out_Of_Service(object_instance)) { + present_value = Binary_Output_Present_Value(object_instance); + if (present_value == BINARY_INACTIVE) { + if (PiFace_Output_State[i]) { + printf("BO-WRITE: OUTPUT[%u]=%d\n", i, + BINARY_LIGHTING_PV_OFF); + piface_write_output(i, BINARY_LIGHTING_PV_OFF); + } + } else { + if (!PiFace_Output_State[i]) { + printf("BO-WRITE: OUTPUT[%u]=%d\n", i, + BINARY_LIGHTING_PV_OFF); + piface_write_output(i, BINARY_LIGHTING_PV_ON); + } + } } } } @@ -209,12 +294,9 @@ int main(int argc, char *argv[]) { BACNET_ADDRESS src = { 0 }; /* address where message came from */ uint16_t pdu_len = 0; - unsigned timeout = 1; /* milliseconds */ - time_t last_seconds = 0; - time_t current_seconds = 0; - uint32_t elapsed_seconds = 0; - uint32_t elapsed_milliseconds = 0; - uint32_t address_binding_tmr = 0; + unsigned timeout_ms = 1; /* milliseconds */ + unsigned long seconds = 0; + unsigned long milliseconds; /* allow the device ID to be set */ if (argc > 1) { @@ -233,41 +315,42 @@ int main(int argc, char *argv[]) atexit(datalink_cleanup); piface_init(); atexit(piface_cleanup); - /* configure the timeout values */ - last_seconds = time(NULL); /* broadcast an I-Am on startup */ Send_I_Am(&Handler_Transmit_Buffer[0]); /* loop forever */ for (;;) { /* input */ - current_seconds = time(NULL); - /* returns 0 bytes on timeout */ - pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); - + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout_ms); /* process */ if (pdu_len) { npdu_handler(&src, &Rx_Buf[0], pdu_len); } - /* at least one second has passed */ - elapsed_seconds = (uint32_t)(current_seconds - last_seconds); - if (elapsed_seconds) { - last_seconds = current_seconds; - dcc_timer_seconds(elapsed_seconds); - datalink_maintenance_timer(elapsed_seconds); - dlenv_maintenance_timer(elapsed_seconds); - elapsed_milliseconds = elapsed_seconds * 1000; - handler_cov_timer_seconds(elapsed_seconds); - tsm_timer_milliseconds(elapsed_milliseconds); + if (mstimer_expired(&BACnet_Task_Timer)) { + mstimer_reset(&BACnet_Task_Timer); + /* 1 second tasks */ + dcc_timer_seconds(1); + datalink_maintenance_timer(1); + dlenv_maintenance_timer(1); + handler_cov_timer_seconds(1); + } + if (mstimer_expired(&BACnet_TSM_Timer)) { + mstimer_reset(&BACnet_TSM_Timer); + tsm_timer_milliseconds(mstimer_interval(&BACnet_TSM_Timer)); } handler_cov_task(); - /* scan cache address */ - address_binding_tmr += elapsed_seconds; - if (address_binding_tmr >= 60) { - address_cache_timer(address_binding_tmr); - address_binding_tmr = 0; + if (mstimer_expired(&BACnet_Address_Timer)) { + mstimer_reset(&BACnet_Address_Timer); + /* address cache */ + seconds = mstimer_interval(&BACnet_Address_Timer) / 1000; + address_cache_timer(seconds); } /* output/input */ + if (mstimer_expired(&BACnet_Object_Timer)) { + mstimer_reset(&BACnet_Object_Timer); + milliseconds = mstimer_interval(&BACnet_Object_Timer); + Device_Timer(milliseconds); + } piface_task(); } diff --git a/apps/server/Makefile b/apps/server/Makefile index e2796d27..e3039448 100644 --- a/apps/server/Makefile +++ b/apps/server/Makefile @@ -11,6 +11,7 @@ SRC = main.c \ $(BACNET_OBJECT_DIR)/av.c \ $(BACNET_OBJECT_DIR)/bi.c \ $(BACNET_OBJECT_DIR)/bo.c \ + $(BACNET_OBJECT_DIR)/blo.c \ $(BACNET_OBJECT_DIR)/bv.c \ $(BACNET_OBJECT_DIR)/channel.c \ $(BACNET_OBJECT_DIR)/color_object.c \ diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index 90976f77..396047dc 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -2206,13 +2206,23 @@ int bacapp_snprintf_value( break; case PROP_PRESENT_VALUE: case PROP_RELINQUISH_DEFAULT: - if (object_type < OBJECT_PROPRIETARY_MIN) { - ret_val = snprintf(str, str_len, "%s", - bactext_binary_present_value_name( - value->type.Enumerated)); - } else { - ret_val = snprintf(str, str_len, "%lu", - (unsigned long)value->type.Enumerated); + switch (object_type) { + case OBJECT_BINARY_INPUT: + case OBJECT_BINARY_OUTPUT: + case OBJECT_BINARY_VALUE: + ret_val = snprintf(str, str_len, "%s", + bactext_binary_present_value_name( + value->type.Enumerated)); + break; + case OBJECT_BINARY_LIGHTING_OUTPUT: + ret_val = snprintf(str, str_len, "%s", + bactext_binary_lighting_pv_name( + value->type.Enumerated)); + break; + default: + ret_val = snprintf(str, str_len, "%lu", + (unsigned long)value->type.Enumerated); + break; } break; case PROP_RELIABILITY: diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index c3bef4ac..64f307b6 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1079,7 +1079,7 @@ typedef enum { PROP_STATE_EXAMPLE_TWO = 257 } BACNET_PROPERTY_STATES; -typedef enum { +typedef enum BACnetReliability { RELIABILITY_NO_FAULT_DETECTED = 0, RELIABILITY_NO_SENSOR = 1, RELIABILITY_OVER_RANGE = 2, @@ -2106,6 +2106,7 @@ typedef enum BACnetBinaryLightingPV { BINARY_LIGHTING_PV_WARN_OFF = 3, BINARY_LIGHTING_PV_WARN_RELINQUISH = 4, BINARY_LIGHTING_PV_STOP = 5, + BINARY_LIGHTING_PV_MAX = 6, /* -- Enumerated values 0-63 are reserved for definition by ASHRAE. -- Enumerated values 64-255 may be used by others -- subject to the procedures and constraints described in Clause 23. */ diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 38394af3..88f3da11 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -1750,12 +1750,41 @@ const char *bactext_lighting_operation_name(unsigned index) } } -bool bactext_bactext_lighting_operation_strtol(const char *search_name, unsigned *found_index) +bool bactext_lighting_operation_strtol(const char *search_name, unsigned *found_index) { return bactext_strtol_index( bacnet_lighting_operation_names, search_name, found_index); } +INDTEXT_DATA bacnet_binary_lighting_pv_names[] = { + { BINARY_LIGHTING_PV_OFF, "off" }, + { BINARY_LIGHTING_PV_ON, "on" }, + { BINARY_LIGHTING_PV_WARN, "warn" }, + { BINARY_LIGHTING_PV_WARN_OFF, "warn-off" }, + { BINARY_LIGHTING_PV_WARN_RELINQUISH, "warn-relinquish" }, + { BINARY_LIGHTING_PV_STOP, "stop" }, + { 0, NULL } +}; + +const char *bactext_binary_lighting_pv_name(unsigned index) +{ + if (index < BINARY_LIGHTING_PV_PROPRIETARY_MIN) { + return indtext_by_index_default( + bacnet_binary_lighting_pv_names, index, ASHRAE_Reserved_String); + } else if (index <= BINARY_LIGHTING_PV_PROPRIETARY_MAX) { + return Vendor_Proprietary_String; + } else { + return "Invalid BACnetBinaryLightingPV"; + } +} + +bool bactext_binary_lighting_pv_names_strtol(const char *search_name, + unsigned *found_index) +{ + return bactext_strtol_index( + bacnet_binary_lighting_pv_names, search_name, found_index); +} + INDTEXT_DATA bacnet_color_operation_names[] = { { BACNET_COLOR_OPERATION_NONE, "none" }, { BACNET_COLOR_OPERATION_FADE_TO_COLOR, "fade-to-color" }, diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h index c834a35c..b5a8a8dc 100644 --- a/src/bacnet/bactext.h +++ b/src/bacnet/bactext.h @@ -201,7 +201,16 @@ extern "C" { unsigned index); BACNET_STACK_EXPORT - bool bactext_bactext_lighting_operation_strtol( + bool bactext_lighting_operation_strtol( + const char *search_name, + unsigned *found_index); + + BACNET_STACK_EXPORT + const char *bactext_binary_lighting_pv_name( + unsigned index); + + BACNET_STACK_EXPORT + bool bactext_binary_lighting_pv_names_strtol( const char *search_name, unsigned *found_index); diff --git a/src/bacnet/basic/object/blo.c b/src/bacnet/basic/object/blo.c new file mode 100644 index 00000000..f437473e --- /dev/null +++ b/src/bacnet/basic/object/blo.c @@ -0,0 +1,1554 @@ +/** + * @file + * @author Steve Karg + * @date 2023 + * @brief Binary Lighting Output object + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacapp.h" +#include "bacnet/config.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" +#include "bacnet/lighting.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/keylist.h" +#include "bacnet/proplist.h" +/* me! */ +#include "bacnet/basic/object/blo.h" + +/* object property values */ +struct object_data { + const char *Object_Name; + const char *Description; + BACNET_RELIABILITY Reliability; + uint32_t Egress_Time; + BACNET_BINARY_LIGHTING_PV Feedback_Value; + BACNET_BINARY_LIGHTING_PV Priority_Array[BACNET_MAX_PRIORITY]; + uint16_t Priority_Active_Bits; + BACNET_BINARY_LIGHTING_PV Relinquish_Default; + float Power; + uint32_t Elapsed_Active_Time; + BACNET_DATE_TIME Time_Of_Active_Time_Reset; + uint32_t Strike_Count; + BACNET_DATE_TIME Time_Of_Strike_Count_Reset; + /* internal operational properties */ + BACNET_BINARY_LIGHTING_PV Target_Value; + uint8_t Target_Priority; + uint32_t Egress_Timer; + /* bit properties */ + bool Out_Of_Service : 1; + bool Blink_Warn_Enable : 1; + bool Egress_Active : 1; + bool Changed : 1; + bool Polarity : 1; +}; +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist Object_List; +/* callback for present value writes */ +static binary_lighting_output_write_value_callback + Binary_Lighting_Output_Write_Value_Callback; +static binary_lighting_output_blink_warn_callback + Binary_Lighting_Output_Blink_Warn_Callback; + +/* These arrays are used by the ReadPropertyMultiple handler and + property-list property (as of protocol-revision 14) */ +static const int Binary_Lighting_Output_Properties_Required[] = { + PROP_OBJECT_IDENTIFIER, PROP_OBJECT_NAME, PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, PROP_STATUS_FLAGS, PROP_OUT_OF_SERVICE, + PROP_BLINK_WARN_ENABLE, PROP_EGRESS_TIME, PROP_EGRESS_ACTIVE, + PROP_PRIORITY_ARRAY, PROP_RELINQUISH_DEFAULT, +#if (BACNET_PROTOCOL_REVISION >= 17) + PROP_CURRENT_COMMAND_PRIORITY, +#endif + -1 +}; +static const int Binary_Lighting_Output_Properties_Optional[] = { + PROP_DESCRIPTION, PROP_RELIABILITY, PROP_FEEDBACK_VALUE, -1 +}; + +static const int Binary_Lighting_Output_Properties_Proprietary[] = { -1 }; + +/** + * Returns the list of required, optional, and proprietary properties. + * Used by ReadPropertyMultiple service. + * + * @param pRequired - pointer to list of int terminated by -1, of + * BACnet required properties for this object. + * @param pOptional - pointer to list of int terminated by -1, of + * BACnet optkional properties for this object. + * @param pProprietary - pointer to list of int terminated by -1, of + * BACnet proprietary properties for this object. + */ +void Binary_Lighting_Output_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary) +{ + if (pRequired) { + *pRequired = Binary_Lighting_Output_Properties_Required; + } + if (pOptional) { + *pOptional = Binary_Lighting_Output_Properties_Optional; + } + if (pProprietary) { + *pProprietary = Binary_Lighting_Output_Properties_Proprietary; + } + + return; +} + +/** + * Determines if a given Lighting Output instance is valid + * + * @param object_instance - object-instance number of the object + * + * @return true if the instance is valid, and false if not + */ +bool Binary_Lighting_Output_Valid_Instance(uint32_t object_instance) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + return true; + } + + return false; +} + +/** + * Determines the number of Lighting Output objects + * + * @return Number of Lighting Output objects + */ +unsigned Binary_Lighting_Output_Count(void) +{ + return Keylist_Count(Object_List); +} + +/** + * Determines the object instance-number for a given 0..N index + * of Lighting Output objects where N is Binary_Lighting_Output_Count(). + * + * @param index - 0..MAX_LIGHTING_OUTPUTS value + * + * @return object instance-number for the given index + */ +uint32_t Binary_Lighting_Output_Index_To_Instance(unsigned index) +{ + return Keylist_Key(Object_List, index); +} + +/** + * For a given object instance-number, determines a 0..N index + * of Lighting Output objects where N is Binary_Lighting_Output_Count(). + * + * @param object_instance - object-instance number of the object + * + * @return index for the given instance-number, or MAX_LIGHTING_OUTPUTS + * if not valid. + */ +unsigned Binary_Lighting_Output_Instance_To_Index(uint32_t object_instance) +{ + return Keylist_Index(Object_List, object_instance); +} + +/** + * @brief Get the priority-array active status for the specific priority + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return the priority-array active status for the specific priority + */ +static bool Priority_Array_Active( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + bool active = false; + + if (priority < BACNET_MAX_PRIORITY) { + if (BIT_CHECK(pObject->Priority_Active_Bits, priority)) { + active = true; + } + } + + return active; +} + +/** + * @brief Get the value of the next highest non-NULL priority, including + * Relinquish_Default + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return The priority-array value for the specific priority + */ +static BACNET_BINARY_LIGHTING_PV Priority_Array_Next_Value( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + unsigned p = 0; + + value = pObject->Relinquish_Default; + for (p = priority; p < BACNET_MAX_PRIORITY; p++) { + if (Priority_Array_Active(pObject, p)) { + value = pObject->Priority_Array[p]; + break; + } + } + + return value; +} + +/** + * For a given object instance-number, determines the present-value + * + * @param object_instance - object-instance number of the object + * + * @return present-value of the object + */ +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Present_Value( + uint32_t object_instance) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = Priority_Array_Next_Value(pObject, 0); + } + + return value; +} + +/** + * @brief Get the priority-array value for the specific priority + * @param object [in] BACnet object instance + * @param priority [in] array index requested: + * 0 to N for individual array members + * @return The priority-array value for the specific priority + */ +static BACNET_BINARY_LIGHTING_PV Priority_Array_Value( + struct object_data *pObject, BACNET_ARRAY_INDEX priority) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + + if (priority < BACNET_MAX_PRIORITY) { + if (BIT_CHECK(pObject->Priority_Active_Bits, priority)) { + value = pObject->Priority_Array[priority]; + } + } + + return value; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param priority [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 Binary_Lighting_Output_Priority_Array_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX priority, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (priority < BACNET_MAX_PRIORITY) { + if (Priority_Array_Active(pObject, priority)) { + value = pObject->Priority_Array[priority]; + apdu_len = encode_application_enumerated(apdu, value); + } else { + apdu_len = encode_application_null(apdu); + } + } + } + + return apdu_len; +} + +/** + * For a given object instance-number, determines the active priority + * + * @param object_instance - object-instance number of the object + * + * @return active priority 1..16, or 0 if no priority is active + */ +static unsigned Present_Value_Priority(struct object_data *pObject) +{ + unsigned p = 0; /* loop counter */ + unsigned priority = 0; /* return value */ + + for (p = 0; p < BACNET_MAX_PRIORITY; p++) { + if (BIT_CHECK(pObject->Priority_Active_Bits, p)) { + priority = p + 1; + break; + } + } + + return priority; +} + +/** + * For a given object instance, relinquishes the present-value + * at a given priority 1..16. + * + * @param object - object instance + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +static bool Present_Value_Relinquish( + struct object_data *pObject, unsigned priority) +{ + bool status = false; + + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */)) { + priority--; + BIT_CLEAR(pObject->Priority_Active_Bits, priority); + pObject->Priority_Array[priority] = BINARY_LIGHTING_PV_OFF; + status = true; + } + + return status; +} + +/** + * For a given object instance, sets the present-value at a given + * priority 1..16. + * + * @param object_instance - object-instance number of the object + * @param value - present value + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +static bool Present_Value_Set(struct object_data *pObject, + BACNET_BINARY_LIGHTING_PV value, + unsigned priority) +{ + bool status = false; + + if (priority && (priority <= BACNET_MAX_PRIORITY) && + (priority != 6 /* reserved */)) { + priority--; + if ((value == BINARY_LIGHTING_PV_OFF) || + (value == BINARY_LIGHTING_PV_ON)) { + /* The logical state of the output shall be either ON or OFF */ + BIT_SET(pObject->Priority_Active_Bits, priority); + pObject->Priority_Array[priority] = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, determines the active priority + * + * @param object_instance - object-instance number of the object + * + * @return active priority 1..16, or 0 if no priority is active + */ +unsigned Binary_Lighting_Output_Present_Value_Priority(uint32_t object_instance) +{ + unsigned priority = 0; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + priority = Present_Value_Priority(pObject); + } + + return priority; +} + +/** + * For a given object instance-number, sets the present-value at a given + * priority 1..16. + * + * @param object_instance - object-instance number of the object + * @param value - present value + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +bool Binary_Lighting_Output_Present_Value_Set(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV value, + unsigned priority) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = Present_Value_Set(pObject, value, priority); + } + + return status; +} + +/** + * For a given object instance-number, handles an ON or OFF target value + * + * @param object_instance - object-instance number of the object + */ +static void Present_Value_On_Off_Handler(uint32_t object_instance) +{ + uint8_t current_priority; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + current_priority = Present_Value_Priority(pObject); + if (pObject->Target_Priority <= current_priority) { + /* we have priority - do something */ + if (pObject->Feedback_Value != pObject->Target_Value) { + if ((!pObject->Out_Of_Service) && + (Binary_Lighting_Output_Write_Value_Callback)) { + Binary_Lighting_Output_Write_Value_Callback(object_instance, + pObject->Feedback_Value, pObject->Target_Value); + } + pObject->Feedback_Value = pObject->Target_Value; + } + pObject->Target_Value = BINARY_LIGHTING_PV_STOP; + pObject->Egress_Timer = 0; + } +} + +/** + * For a given object instance-number, handles an ON or OFF target value + * + * @param object_instance - object-instance number of the object + */ +static void Present_Value_Relinquish_Handler(uint32_t object_instance) +{ + uint8_t current_priority; + BACNET_BINARY_LIGHTING_PV value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + current_priority = Present_Value_Priority(pObject); + if (pObject->Target_Priority != current_priority) { + /* target priority holds previous priority + and *any* change after relinquish + indicates something needs done */ + if (current_priority > BACNET_MAX_PRIORITY) { + value = pObject->Relinquish_Default; + } else { + value = Priority_Array_Value(pObject, current_priority); + } + if (pObject->Feedback_Value != value) { + pObject->Changed = true; + if ((!pObject->Out_Of_Service) && + (Binary_Lighting_Output_Write_Value_Callback)) { + Binary_Lighting_Output_Write_Value_Callback( + object_instance, pObject->Feedback_Value, value); + } + pObject->Feedback_Value = value; + } + pObject->Target_Value = BINARY_LIGHTING_PV_STOP; + } +} + +/** + * For a given object instance, handles a WARN target value + * + * WARN + * Executes a blink-warn notification at the + * specified priority. After the blink-warn + * notification has been executed the value + * at the specified priority remains ON. + * + * WARN_OFF + * Executes a blink-warn notification at the + * specified priority and then writes the value OFF + * to the specified slot in the priority array + * after a delay of Egress_Time seconds. + * + * WARN_RELINQUISH + * Executes a blink-warn notification at the + * specified priority and then relinquishes the value + * at the specified priority slot + * after a delay of Egress_Time seconds. + * + * The blink-warn notification shall not occur + * if any of the following conditions occur: + * (a) The specified priority is not the highest priority, or + * (b) The value at the specified priority is OFF, or + * (c) Blink_Warn_Enable is FALSE. + * + * In the case of WARN_RELINQUISH, + * (d) The value at the specified priority is NULL, or + * (e) The value of the next highest non-NULL priority, + * including Relinquish_Default, is ON. + * + * @param object_instance - object-instance number of the object + */ +static void Present_Value_Warn_Handler(uint32_t object_instance) +{ + uint8_t current_priority; + BACNET_BINARY_LIGHTING_PV lighting_value; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + return; + } + current_priority = Present_Value_Priority(pObject); + if (pObject->Target_Value == BINARY_LIGHTING_PV_WARN_RELINQUISH) { + /* relinquish this priority */ + Present_Value_Relinquish(pObject, pObject->Target_Priority); + } + if (pObject->Target_Priority > current_priority) { + /* The specified priority is not the highest priority */ + return; + } + lighting_value = Priority_Array_Next_Value(pObject, 0); + if (lighting_value == BINARY_LIGHTING_PV_OFF) { + /* The value at the specified priority is OFF */ + return; + } + if (!pObject->Blink_Warn_Enable) { + /* Blink_Warn_Enable is FALSE */ + return; + } + if (pObject->Target_Value == BINARY_LIGHTING_PV_WARN_RELINQUISH) { + if (!Priority_Array_Active(pObject, pObject->Target_Priority)) { + /* The value at the specified priority is NULL */ + return; + } + lighting_value = + Priority_Array_Next_Value(pObject, pObject->Target_Priority); + if (lighting_value == BINARY_LIGHTING_PV_ON) { + /* The value of the next highest non-NULL priority, + including Relinquish_Default, is ON. */ + return; + } + pObject->Target_Priority = Present_Value_Priority(pObject); + } + /* the egress time in seconds when a WARN_RELINQUISH or WARN_OFF value + is written to the Present_Value property. */ + pObject->Egress_Timer = 1000UL * pObject->Egress_Time; + /* warn at least once */ + if ((!pObject->Out_Of_Service) && + (Binary_Lighting_Output_Blink_Warn_Callback)) { + Binary_Lighting_Output_Blink_Warn_Callback(object_instance); + } + /* what to do after egress expires */ + if (pObject->Target_Value == BINARY_LIGHTING_PV_WARN) { + pObject->Target_Value = BINARY_LIGHTING_PV_ON; + } else if (pObject->Target_Value == BINARY_LIGHTING_PV_WARN_OFF) { + pObject->Target_Value = BINARY_LIGHTING_PV_OFF; + } else if (pObject->Target_Value == + BINARY_LIGHTING_PV_WARN_RELINQUISH) { + pObject->Target_Value = BINARY_LIGHTING_PV_OFF; + } +} + +/** + * @brief Updates the lighting object feedback value per present-value + * @param object_instance - object-instance number of the object + * @param milliseconds - number of milliseconds elapsed since previously + * called. Suggest that this is called every 1000 milliseconds. + */ +void Binary_Lighting_Output_Timer( + uint32_t object_instance, uint16_t milliseconds) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Egress_Timer > milliseconds) { + pObject->Egress_Timer -= milliseconds; + if ((!pObject->Out_Of_Service) && + (Binary_Lighting_Output_Blink_Warn_Callback)) { + Binary_Lighting_Output_Blink_Warn_Callback(object_instance); + } + return; + } else { + pObject->Egress_Timer = 0; + } + switch (pObject->Target_Value) { + case BINARY_LIGHTING_PV_OFF: + Present_Value_On_Off_Handler(object_instance); + break; + case BINARY_LIGHTING_PV_ON: + Present_Value_On_Off_Handler(object_instance); + break; + case BINARY_LIGHTING_PV_WARN: + Present_Value_Warn_Handler(object_instance); + break; + case BINARY_LIGHTING_PV_WARN_OFF: + /* Executes a blink-warn notification at the + specified priority and then writes the value OFF + to the specified slot in the priority array after + a delay of Egress_Time seconds. */ + break; + case BINARY_LIGHTING_PV_WARN_RELINQUISH: + break; + case BINARY_LIGHTING_PV_STOP: + break; + default: + break; + } + } +} + +/** + * For a given object instance-number, writes the present-value + * + * @param object_instance - object-instance number of the object + * @param value - property value to write + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Binary_Lighting_Output_Present_Value_Write(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV value, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (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 ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + if (value < BINARY_LIGHTING_PV_MAX) { + pObject->Target_Value = value; + pObject->Target_Priority = priority; + status = Present_Value_Set(pObject, value, priority); + if (status) { + /* ON or OFF only */ + Present_Value_On_Off_Handler(object_instance); + } + status = true; + } else if ((value >= BINARY_LIGHTING_PV_PROPRIETARY_MIN) && + (value <= BINARY_LIGHTING_PV_PROPRIETARY_MAX)) { + pObject->Target_Priority = priority; + pObject->Target_Value = value; + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, relinquishes the present-value + * at a given priority 1..16. + * + * @param object_instance - object-instance number of the object + * @param priority - priority 1..16 + * + * @return true if values are within range and present-value is set. + */ +bool Binary_Lighting_Output_Present_Value_Relinquish( + uint32_t object_instance, unsigned priority) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = Present_Value_Relinquish(pObject, priority); + } + + return status; +} + +/** + * For a given object instance-number, relinquishes the present-value + * + * @param object_instance - object-instance number of the object + * @param value - property value to write + * @param priority - priority-array index value 1..16 + * @param error_class - the BACnet error class + * @param error_code - BACnet Error code + * + * @return true if values are within range and present-value is set. + */ +static bool Binary_Lighting_Output_Present_Value_Relinquish_Write( + uint32_t object_instance, + uint8_t priority, + BACNET_ERROR_CLASS *error_class, + BACNET_ERROR_CODE *error_code) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (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 ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + /* target priority will hold the previous priority */ + pObject->Target_Priority = Present_Value_Priority(pObject); + pObject->Target_Value = BINARY_LIGHTING_PV_STOP; + Present_Value_Relinquish(pObject, priority); + Present_Value_Relinquish_Handler(object_instance); + status = true; + } else { + *error_class = ERROR_CLASS_PROPERTY; + *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + *error_class = ERROR_CLASS_OBJECT; + *error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, loads the object-name into + * a characterstring. Note that the object name must be unique + * within this device. + * + * @param object_instance - object-instance number of the object + * @param object_name - holds the object-name retrieved + * + * @return true if object-name was retrieved + */ +bool Binary_Lighting_Output_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name) +{ + bool status = false; + struct object_data *pObject; + char name_text[32] = "BINARY-LIGHTING-OUTPUT-4194303"; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Object_Name) { + status = + characterstring_init_ansi(object_name, pObject->Object_Name); + } else { + snprintf(name_text, sizeof(name_text), "BINARY-LIGHTING-OUTPUT-%u", + object_instance); + status = characterstring_init_ansi(object_name, name_text); + } + } + + return status; +} + +/** + * For a given object instance-number, sets the object-name + * + * @param object_instance - object-instance number of the object + * @param new_name - holds the object-name to be set + * + * @return true if object-name was set + */ +bool Binary_Lighting_Output_Name_Set(uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + status = true; + pObject->Object_Name = new_name; + } + + return status; +} + +/** + * For a given object instance-number, returns the description + * + * @param object_instance - object-instance number of the object + * + * @return description text or NULL if not found + */ +char *Binary_Lighting_Output_Description(uint32_t object_instance) +{ + char *name = NULL; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Description) { + name = (char *)pObject->Description; + } else { + name = ""; + } + } + + return name; +} + +/** + * For a given object instance-number, sets the description + * + * @param object_instance - object-instance number of the object + * @param new_name - holds the description to be set + * + * @return true if object-name was set + */ +bool Binary_Lighting_Output_Description_Set( + uint32_t object_instance, char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject && new_name) { + status = true; + pObject->Description = new_name; + } + + return status; +} + +/** + * For a given object instance-number, sets the lighting command value + * + * @param object_instance - object-instance number of the object + * @param value - holds the target lighting value + * @param priority - holds the target priority value + * + * @return true if lighting target value was set + */ +bool Binary_Lighting_Output_Lighting_Command_Set(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV value, + unsigned priority) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Target_Priority = priority; + pObject->Target_Value = value; + } + + return status; +} + +/** + * For a given object instance-number, gets the lighting-command target value + * + * @param object_instance - object-instance number of the object + * @return lighting command target value + */ +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Lighting_Command_Target_Value( + uint32_t object_instance) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Target_Value; + } + + return value; +} + +/** + * For a given object instance-number, gets the lighting-command target value + * + * @param object_instance - object-instance number of the object + * @return lighting command target priority + */ +unsigned Binary_Lighting_Output_Lighting_Command_Target_Priority( + uint32_t object_instance) +{ + unsigned priority = BACNET_MAX_PRIORITY; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + priority = pObject->Target_Priority; + } + + return priority; +} + +/** + * For a given object instance-number, gets the tracking-value property + * + * @param object_instance - object-instance number of the object + * + * @return the tracking-value of this object instance. + */ +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Feedback_Value( + uint32_t object_instance) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Feedback_Value; + } + + return value; +} + +/** + * For a given object instance-number, sets the specific property value of the + * object. + * + * @param object_instance - object-instance number of the object + * @param value - holds the value to be set + * + * @return true if value was set + */ +bool Binary_Lighting_Output_Feedback_Value_Set( + uint32_t object_instance, BACNET_BINARY_LIGHTING_PV value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((value == BINARY_LIGHTING_PV_OFF) || + (value == BINARY_LIGHTING_PV_ON)) { + /* This property shall have the value + ON (i.e. light is physically on) or + OFF (i.e. light is physically off). */ + pObject->Feedback_Value = value; + status = true; + } + } + + return status; +} + +/** + * For a given object instance-number, gets the blink-warn-enable + * property value + * + * @param object_instance - object-instance number of the object + * + * @return the blink-warn-enable property value of this object + */ +bool Binary_Lighting_Output_Blink_Warn_Enable(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Blink_Warn_Enable; + } + + return value; +} + +/** + * For a given object instance-number, sets the blink-warn-enable + * property value in the object. + * + * @param object_instance - object-instance number of the object + * @param enable - holds the value to be set + * + * @return true if value was set + */ +bool Binary_Lighting_Output_Blink_Warn_Enable_Set( + uint32_t object_instance, bool enable) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Blink_Warn_Enable = enable; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the egress-time + * property value + * + * @param object_instance - object-instance number of the object + * + * @return the egress-time property value of this object + */ +uint32_t Binary_Lighting_Output_Egress_Time(uint32_t object_instance) +{ + uint32_t value = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Egress_Time; + } + + return value; +} + +/** + * For a given object instance-number, sets the egress-time + * property value of the object. + * + * @param object_instance - object-instance number of the object + * @param seconds - holds the value to be set + * + * @return true if value was set + */ +bool Binary_Lighting_Output_Egress_Time_Set( + uint32_t object_instance, uint32_t seconds) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Egress_Time = seconds; + status = true; + } + + return status; +} + +/** + * For a given object instance-number, gets the egress-active + * property value + * + * @param object_instance - object-instance number of the object + * + * @return the egress-active property value of this object + */ +bool Binary_Lighting_Output_Egress_Active(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Egress_Timer > 0 ? true : false; + } + + return value; +} + +/** + * For a given object instance-number, returns the out-of-service + * property value + * + * @param object_instance - object-instance number of the object + * + * @return out-of-service property value + */ +bool Binary_Lighting_Output_Out_Of_Service(uint32_t object_instance) +{ + bool value = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Out_Of_Service; + } + + return value; +} + +/** + * For a given object instance-number, sets the out-of-service property value + * + * @param object_instance - object-instance number of the object + * @param value - boolean out-of-service value + * + * @return true if the out-of-service property value was set + */ +void Binary_Lighting_Output_Out_Of_Service_Set( + uint32_t object_instance, bool value) +{ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Out_Of_Service = value; + } +} + +/** + * For a given object instance-number, returns the relinquish-default + * property value + * + * @param object_instance - object-instance number of the object + * + * @return relinquish-default property value + */ +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Relinquish_Default( + uint32_t object_instance) +{ + BACNET_BINARY_LIGHTING_PV value = BINARY_LIGHTING_PV_OFF; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Relinquish_Default; + } + + return value; +} + +/** + * For a given object instance-number, sets the relinquish-default + * property value + * + * @param object_instance - object-instance number of the object + * @param value - floating point relinquish-default value + * + * @return true if the relinquish-default property value was set + */ +bool Binary_Lighting_Output_Relinquish_Default_Set( + uint32_t object_instance, BACNET_BINARY_LIGHTING_PV value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if ((value == BINARY_LIGHTING_PV_OFF) || + (value == BINARY_LIGHTING_PV_ON)) { + pObject->Relinquish_Default = value; + } + } + + return status; +} + +/** + * For a given object instance-number, returns the property value + * property value + * + * @param object_instance - object-instance number of the object + * + * @return property value + */ +BACNET_RELIABILITY Binary_Lighting_Output_Reliability(uint32_t object_instance) +{ + BACNET_RELIABILITY value = RELIABILITY_NO_FAULT_DETECTED; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + value = pObject->Reliability; + } + + return value; +} + +/** + * For a given object instance-number, sets the property value + * + * @param object_instance - object-instance number of the object + * @param value - property value + * + * @return true if the property value was set + */ +bool Binary_Lighting_Output_Reliability_Set( + uint32_t object_instance, BACNET_RELIABILITY value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + pObject->Reliability = value; + status = true; + } + + return status; +} + +/** + * ReadProperty handler for this object. For the given ReadProperty + * data, the application_data is loaded or the error flags are set. + * + * @param rpdata - ReadProperty data, including requested data and + * data for the reply, or error response. + * + * @return number of APDU bytes in the response, or + * BACNET_STATUS_ERROR on error. + */ +int Binary_Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) +{ + int apdu_len = 0; /* return value */ + int apdu_size = 0; + BACNET_BIT_STRING bit_string; + BACNET_CHARACTER_STRING char_string; + BACNET_BINARY_LIGHTING_PV lighting_value; + uint32_t unsigned_value = 0; + unsigned i = 0; + bool state = false; + uint8_t *apdu = NULL; + + if ((rpdata == NULL) || (rpdata->application_data == NULL) || + (rpdata->application_data_len == 0)) { + return 0; + } + apdu = rpdata->application_data; + apdu_size = rpdata->application_data_len; + switch (rpdata->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = encode_application_object_id( + &apdu[0], rpdata->object_type, rpdata->object_instance); + break; + case PROP_OBJECT_NAME: + Binary_Lighting_Output_Object_Name( + rpdata->object_instance, &char_string); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], OBJECT_LIGHTING_OUTPUT); + break; + case PROP_PRESENT_VALUE: + lighting_value = + Binary_Lighting_Output_Present_Value(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], lighting_value); + break; + case PROP_FEEDBACK_VALUE: + lighting_value = + Binary_Lighting_Output_Feedback_Value(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], lighting_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); + state = + Binary_Lighting_Output_Out_Of_Service(rpdata->object_instance); + bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, state); + apdu_len = encode_application_bitstring(&apdu[0], &bit_string); + break; + case PROP_OUT_OF_SERVICE: + state = + Binary_Lighting_Output_Out_Of_Service(rpdata->object_instance); + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_BLINK_WARN_ENABLE: + state = Binary_Lighting_Output_Blink_Warn_Enable( + rpdata->object_instance); + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_EGRESS_TIME: + unsigned_value = + Binary_Lighting_Output_Egress_Time(rpdata->object_instance); + apdu_len = encode_application_unsigned(&apdu[0], unsigned_value); + break; + case PROP_EGRESS_ACTIVE: + state = + Binary_Lighting_Output_Egress_Active(rpdata->object_instance); + apdu_len = encode_application_boolean(&apdu[0], state); + break; + case PROP_PRIORITY_ARRAY: + apdu_len = bacnet_array_encode(rpdata->object_instance, + rpdata->array_index, + Binary_Lighting_Output_Priority_Array_Encode, + BACNET_MAX_PRIORITY, apdu, apdu_size); + 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; + case PROP_RELINQUISH_DEFAULT: + lighting_value = Binary_Lighting_Output_Relinquish_Default( + rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], lighting_value); + break; +#if (BACNET_PROTOCOL_REVISION >= 17) + case PROP_CURRENT_COMMAND_PRIORITY: + i = Binary_Lighting_Output_Present_Value_Priority( + rpdata->object_instance); + if ((i >= BACNET_MIN_PRIORITY) && (i <= BACNET_MAX_PRIORITY)) { + apdu_len = encode_application_unsigned(&apdu[0], i); + } else { + apdu_len = encode_application_null(&apdu[0]); + } + break; +#endif + case PROP_DESCRIPTION: + characterstring_init_ansi(&char_string, + Binary_Lighting_Output_Description(rpdata->object_instance)); + apdu_len = + encode_application_character_string(&apdu[0], &char_string); + break; + case PROP_RELIABILITY: + unsigned_value = + Binary_Lighting_Output_Reliability(rpdata->object_instance); + apdu_len = encode_application_enumerated(&apdu[0], unsigned_value); + break; + default: + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + /* only array properties can have array options */ + if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) && + (rpdata->array_index != BACNET_ARRAY_ALL)) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + apdu_len = BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Binary_Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_APPLICATION_DATA_VALUE value; + + /* 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? */ + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + if ((wp_data->object_property != PROP_PRIORITY_ARRAY) && + (wp_data->array_index != BACNET_ARRAY_ALL)) { + /* only array properties can have array options */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + switch (wp_data->object_property) { + case PROP_PRESENT_VALUE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + status = Binary_Lighting_Output_Present_Value_Write( + wp_data->object_instance, value.type.Enumerated, + wp_data->priority, &wp_data->error_class, + &wp_data->error_code); + } else { + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_NULL); + if (status) { + status = + Binary_Lighting_Output_Present_Value_Relinquish_Write( + wp_data->object_instance, wp_data->priority, + &wp_data->error_class, &wp_data->error_code); + } + } + break; + case PROP_OUT_OF_SERVICE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); + if (status) { + Binary_Lighting_Output_Out_Of_Service_Set( + wp_data->object_instance, value.type.Boolean); + } + break; + case PROP_OBJECT_IDENTIFIER: + case PROP_OBJECT_NAME: + case PROP_OBJECT_TYPE: + case PROP_TRACKING_VALUE: + case PROP_IN_PROGRESS: + case PROP_STATUS_FLAGS: + case PROP_BLINK_WARN_ENABLE: + case PROP_EGRESS_TIME: + case PROP_EGRESS_ACTIVE: + case PROP_PRIORITY_ARRAY: + case PROP_RELINQUISH_DEFAULT: + case PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY: +#if (BACNET_PROTOCOL_REVISION >= 17) + case PROP_CURRENT_COMMAND_PRIORITY: +#endif + case PROP_DESCRIPTION: + case PROP_RELIABILITY: + case PROP_FEEDBACK_VALUE: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + break; + default: + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + break; + } + + return status; +} + +/** + * @brief Sets a callback used when present-value is written from BACnet + * @param cb - callback used to provide indications + */ +void Binary_Lighting_Output_Write_Value_Callback_Set( + binary_lighting_output_write_value_callback cb) +{ + Binary_Lighting_Output_Write_Value_Callback = cb; +} + +/** + * @brief Sets a callback used when for blink warning notification + * @param cb - callback used to provide indications + */ +void Binary_Lighting_Output_Blink_Warn_Callback_Set( + binary_lighting_output_blink_warn_callback cb) +{ + Binary_Lighting_Output_Blink_Warn_Callback = cb; +} + +/** + * @brief Creates a Color object + * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE + */ +uint32_t Binary_Lighting_Output_Create(uint32_t object_instance) +{ + struct object_data *pObject = NULL; + int index = 0; + unsigned p = 0; + + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } + pObject = Keylist_Data(Object_List, object_instance); + if (!pObject) { + pObject = calloc(1, sizeof(struct object_data)); + if (!pObject) { + return BACNET_MAX_INSTANCE; + } + pObject->Object_Name = NULL; + pObject->Description = NULL; + pObject->Target_Priority = BACNET_MAX_PRIORITY; + pObject->Out_Of_Service = false; + pObject->Blink_Warn_Enable = false; + pObject->Egress_Active = false; + pObject->Egress_Time = 0; + pObject->Feedback_Value = BINARY_LIGHTING_PV_OFF; + pObject->Target_Value = BINARY_LIGHTING_PV_OFF; + for (p = 0; p < BACNET_MAX_PRIORITY; p++) { + pObject->Priority_Array[p] = BINARY_LIGHTING_PV_OFF; + BIT_CLEAR(pObject->Priority_Active_Bits, p); + } + pObject->Relinquish_Default = BINARY_LIGHTING_PV_OFF; + pObject->Power = 0.0; + /* add to list */ + index = Keylist_Data_Add(Object_List, object_instance, pObject); + if (index < 0) { + free(pObject); + return BACNET_MAX_INSTANCE; + } + } + + return object_instance; +} + +/** + * Deletes an object instance + * @param object_instance - object-instance number of the object + * @return true if the object is deleted + */ +bool Binary_Lighting_Output_Delete(uint32_t object_instance) +{ + bool status = false; + struct object_data *pObject = NULL; + + pObject = Keylist_Data_Delete(Object_List, object_instance); + if (pObject) { + free(pObject); + status = true; + } + + return status; +} + +/** + * Deletes all the objects and their data + */ +void Binary_Lighting_Output_Cleanup(void) +{ + struct object_data *pObject; + + if (Object_List) { + do { + pObject = Keylist_Data_Pop(Object_List); + if (pObject) { + free(pObject); + } + } while (pObject); + Keylist_Delete(Object_List); + Object_List = NULL; + } +} + +/** + * Initializes the object list + */ +void Binary_Lighting_Output_Init(void) +{ + Object_List = Keylist_Create(); +} diff --git a/src/bacnet/basic/object/blo.h b/src/bacnet/basic/object/blo.h new file mode 100644 index 00000000..a0296844 --- /dev/null +++ b/src/bacnet/basic/object/blo.h @@ -0,0 +1,161 @@ +/** + * @file + * @author Steve Karg + * @date 2023 + * @brief Binary Lighting Output object + * + * SPDX-License-Identifier: MIT + */ +#ifndef BINARY_LIGHTING_OUTPUT_H +#define BINARY_LIGHTING_OUTPUT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacerror.h" +#include "bacnet/rp.h" +#include "bacnet/wp.h" + +/** + * @brief Callback for write value request + * @param object_instance - object-instance number of the object + * @param old_value - value prior to write + * @param value - value of the write + */ +typedef void (*binary_lighting_output_write_value_callback)( + uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV old_value, + BACNET_BINARY_LIGHTING_PV value); + +/** + * @brief Callback for blink warning notification + * @param object_instance - object-instance number of the object + */ +typedef void (*binary_lighting_output_blink_warn_callback)( + uint32_t object_instance); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Property_Lists( + const int **pRequired, const int **pOptional, const int **pProprietary); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Valid_Instance(uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Binary_Lighting_Output_Count(void); +BACNET_STACK_EXPORT +uint32_t Binary_Lighting_Output_Index_To_Instance(unsigned index); +BACNET_STACK_EXPORT +unsigned Binary_Lighting_Output_Instance_To_Index(uint32_t instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Object_Instance_Add(uint32_t instance); + +BACNET_STACK_EXPORT +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Present_Value( + uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Binary_Lighting_Output_Present_Value_Priority( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Present_Value_Set(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV value, + unsigned priority); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Present_Value_Relinquish( + uint32_t object_instance, unsigned priority); + +BACNET_STACK_EXPORT +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Relinquish_Default( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Relinquish_Default_Set( + uint32_t object_instance, BACNET_BINARY_LIGHTING_PV value); + +BACNET_STACK_EXPORT +BACNET_RELIABILITY Binary_Lighting_Output_Reliability( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Reliability_Set( + uint32_t object_instance, BACNET_RELIABILITY value); + +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Object_Name( + uint32_t object_instance, BACNET_CHARACTER_STRING *object_name); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Name_Set(uint32_t object_instance, char *new_name); + +BACNET_STACK_EXPORT +char *Binary_Lighting_Output_Description(uint32_t instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Description_Set(uint32_t instance, char *new_name); + +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Out_Of_Service(uint32_t instance); +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Out_Of_Service_Set( + uint32_t instance, bool oos_flag); + +BACNET_STACK_EXPORT +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Lighting_Command_Target_Value( + uint32_t object_instance); +BACNET_STACK_EXPORT +unsigned Binary_Lighting_Output_Lighting_Command_Target_Priority( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Lighting_Command_Set( + uint32_t object_instance, BACNET_BINARY_LIGHTING_PV value, unsigned priority); + +BACNET_STACK_EXPORT +BACNET_BINARY_LIGHTING_PV Binary_Lighting_Output_Feedback_Value( + uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Feedback_Value_Set( + uint32_t object_instance, BACNET_BINARY_LIGHTING_PV value); + +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Blink_Warn_Enable(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Blink_Warn_Enable_Set( + uint32_t object_instance, bool enable); + +BACNET_STACK_EXPORT +uint32_t Binary_Lighting_Output_Egress_Time(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Egress_Time_Set( + uint32_t object_instance, uint32_t seconds); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Egress_Active(uint32_t object_instance); + +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Timer( + uint32_t object_instance, uint16_t milliseconds); + +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Write_Value_Callback_Set( + binary_lighting_output_write_value_callback cb); + +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Blink_Warn_Callback_Set( + binary_lighting_output_blink_warn_callback cb); + +BACNET_STACK_EXPORT +uint32_t Binary_Lighting_Output_Create(uint32_t object_instance); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Delete(uint32_t object_instance); +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Cleanup(void); +BACNET_STACK_EXPORT +void Binary_Lighting_Output_Init(void); + +BACNET_STACK_EXPORT +int Binary_Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); +BACNET_STACK_EXPORT +bool Binary_Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 8091cbd1..fd73658f 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -51,6 +51,9 @@ #include "bacnet/basic/object/ao.h" #include "bacnet/basic/object/av.h" #include "bacnet/basic/object/bi.h" +#if (BACNET_PROTOCOL_REVISION >= 16) +#include "bacnet/basic/object/blo.h" +#endif #include "bacnet/basic/object/bo.h" #include "bacnet/basic/object/bv.h" #include "bacnet/basic/object/channel.h" @@ -267,6 +270,20 @@ static object_functions_t My_Object_Table[] = { NULL /* Add_List_Element */, NULL /* Remove_List_Element */, Channel_Create, Channel_Delete, NULL /* Timer */ }, #endif +#if (BACNET_PROTOCOL_REVISION >= 16) + { OBJECT_BINARY_LIGHTING_OUTPUT, Binary_Lighting_Output_Init, + Binary_Lighting_Output_Count, Binary_Lighting_Output_Index_To_Instance, + Binary_Lighting_Output_Valid_Instance, + Binary_Lighting_Output_Object_Name, + Binary_Lighting_Output_Read_Property, + Binary_Lighting_Output_Write_Property, + Binary_Lighting_Output_Property_Lists, NULL /* ReadRangeInfo */, + NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, + NULL /* COV Clear */, NULL /* Intrinsic Reporting */, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Lighting_Output_Create, Binary_Lighting_Output_Delete, + Binary_Lighting_Output_Timer }, +#endif #if (BACNET_PROTOCOL_REVISION >= 24) { OBJECT_COLOR, Color_Init, Color_Count, Color_Index_To_Instance, Color_Valid_Instance, Color_Object_Name, Color_Read_Property, @@ -2201,12 +2218,14 @@ void Device_Init(object_functions_t *object_table) pObject++; } /* create some dynamically created objects as examples */ - pObject = Object_Table; - while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { - if (pObject->Object_Create) { - pObject->Object_Create(BACNET_MAX_INSTANCE); + if (!object_table) { + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Create) { + pObject->Object_Create(BACNET_MAX_INSTANCE); + } + pObject++; } - pObject++; } #if (BACNET_PROTOCOL_REVISION >= 14) Channel_Write_Property_Internal_Callback_Set(Device_Write_Property); diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c index 70f1309b..f44f4618 100644 --- a/src/bacnet/basic/object/lo.c +++ b/src/bacnet/basic/object/lo.c @@ -48,10 +48,6 @@ /* me! */ #include "bacnet/basic/object/lo.h" -#ifndef MAX_LIGHTING_OUTPUTS -#define MAX_LIGHTING_OUTPUTS 8 -#endif - struct object_data { float Present_Value; float Tracking_Value; diff --git a/src/bacnet/basic/sys/keylist.h b/src/bacnet/basic/sys/keylist.h index d832949d..940c5117 100644 --- a/src/bacnet/basic/sys/keylist.h +++ b/src/bacnet/basic/sys/keylist.h @@ -25,7 +25,7 @@ #define KEYLIST_H #include "bacnet/bacnet_stack_exports.h" -#include "key.h" +#include "bacnet/basic/sys/key.h" /* This is a key sorted linked list data library that */ /* uses a key or index to access the data. */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 10aa75e2..022ea581 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -113,6 +113,7 @@ list(APPEND testdirs bacnet/basic/object/av bacnet/basic/object/bacfile bacnet/basic/object/bi + bacnet/basic/object/blo bacnet/basic/object/bo bacnet/basic/object/bv bacnet/basic/object/channel diff --git a/test/bacnet/basic/object/blo/CMakeLists.txt b/test/bacnet/basic/object/blo/CMakeLists.txt new file mode 100644 index 00000000..011d28a2 --- /dev/null +++ b/test/bacnet/basic/object/blo/CMakeLists.txt @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/object/blo.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacerror.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/basic/sys/linear.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/wp.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + # Test and test library files + ./src/main.c + ../mock/device_mock.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + m) diff --git a/test/bacnet/basic/object/blo/src/main.c b/test/bacnet/basic/object/blo/src/main.c new file mode 100644 index 00000000..93b6fc50 --- /dev/null +++ b/test/bacnet/basic/object/blo/src/main.c @@ -0,0 +1,277 @@ +/** + * @file + * @brief Unit test for object + * @author Steve Karg + * @date September 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** + * @brief Test + */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(lo_tests, testBinaryLightingOutput) +#else +static void testBinaryLightingOutput(void) +#endif +{ + uint8_t apdu[MAX_APDU] = { 0 }; + int len = 0, test_len = 0; + BACNET_READ_PROPERTY_DATA rpdata; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + const int *pRequired = NULL; + const int *pOptional = NULL; + const int *pProprietary = NULL; + const uint32_t instance = 123; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + bool status = false; + unsigned index; + + Binary_Lighting_Output_Init(); + Binary_Lighting_Output_Create(instance); + status = Binary_Lighting_Output_Valid_Instance(instance); + zassert_true(status, NULL); + index = Binary_Lighting_Output_Instance_To_Index(instance); + zassert_equal(index, 0, NULL); + + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = OBJECT_BINARY_LIGHTING_OUTPUT; + rpdata.object_instance = instance; + rpdata.array_index = BACNET_ARRAY_ALL; + + Binary_Lighting_Output_Property_Lists( + &pRequired, &pOptional, &pProprietary); + while ((*pRequired) >= 0) { + rpdata.object_property = *pRequired; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Binary_Lighting_Output_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len >= 0) { + test_len = bacapp_decode_known_property(rpdata.application_data, + len, &value, rpdata.object_type, rpdata.object_property); + if (rpdata.object_property != PROP_PRIORITY_ARRAY) { + zassert_equal(len, test_len, + "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + } + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Binary_Lighting_Output_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pRequired++; + } + while ((*pOptional) != -1) { + rpdata.object_property = *pOptional; + rpdata.array_index = BACNET_ARRAY_ALL; + len = Binary_Lighting_Output_Read_Property(&rpdata); + zassert_not_equal(len, BACNET_STATUS_ERROR, + "property '%s': failed to ReadProperty!\n", + bactext_property_name(rpdata.object_property)); + if (len > 0) { + test_len = bacapp_decode_application_data(rpdata.application_data, + (uint8_t)rpdata.application_data_len, &value); + zassert_equal(len, test_len, "property '%s': failed to decode!\n", + bactext_property_name(rpdata.object_property)); + /* check WriteProperty properties */ + wpdata.object_type = rpdata.object_type; + wpdata.object_instance = rpdata.object_instance; + wpdata.object_property = rpdata.object_property; + wpdata.array_index = rpdata.array_index; + memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); + wpdata.application_data_len = len; + wpdata.error_code = ERROR_CODE_SUCCESS; + status = Binary_Lighting_Output_Write_Property(&wpdata); + if (!status) { + /* verify WriteProperty property is known */ + zassert_not_equal(wpdata.error_code, + ERROR_CODE_UNKNOWN_PROPERTY, + "property '%s': WriteProperty Unknown!\n", + bactext_property_name(rpdata.object_property)); + } + } + pOptional++; + } + /* check for unsupported property - use ALL */ + rpdata.object_property = PROP_ALL; + len = Binary_Lighting_Output_Read_Property(&rpdata); + zassert_equal(len, BACNET_STATUS_ERROR, NULL); + status = Binary_Lighting_Output_Write_Property(&wpdata); + zassert_false(status, NULL); + /* check the delete function */ + status = Binary_Lighting_Output_Delete(instance); + zassert_true(status, NULL); + + return; +} + +static struct { + uint32_t object_instance; + BACNET_BINARY_LIGHTING_PV old_pv; + BACNET_BINARY_LIGHTING_PV pv; + uint32_t count; +} BLO_Value; +static void Binary_Lighting_Output_Write_Value_Handler(uint32_t object_instance, + BACNET_BINARY_LIGHTING_PV old_value, + BACNET_BINARY_LIGHTING_PV value) +{ + BLO_Value.object_instance = object_instance; + BLO_Value.old_pv = old_value; + BLO_Value.pv = value; + BLO_Value.count++; +} + +static struct { + uint32_t object_instance; + uint32_t count; +} BLO_Blink; +static void Binary_Lighting_Output_Blink_Warn_Handler(uint32_t object_instance) +{ + BLO_Blink.object_instance = object_instance; + BLO_Blink.count++; +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(lo_tests, testBinaryLightingOutputBlink) +#else +static void testBinaryLightingOutputBlink(void) +#endif +{ + const uint32_t object_instance = 123; + bool status = false; + uint16_t milliseconds, milliseconds_elapsed; + BACNET_BINARY_LIGHTING_PV pv, test_pv, expect_pv; + unsigned test_priority; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + + Binary_Lighting_Output_Init(); + Binary_Lighting_Output_Create(object_instance); + status = Binary_Lighting_Output_Valid_Instance(object_instance); + zassert_true(status, NULL); + Binary_Lighting_Output_Write_Value_Callback_Set( + Binary_Lighting_Output_Write_Value_Handler); + Binary_Lighting_Output_Blink_Warn_Callback_Set( + Binary_Lighting_Output_Blink_Warn_Handler); + /* check the blink warning engine at defaults */ + milliseconds_elapsed = 100; + expect_pv = BINARY_LIGHTING_PV_OFF; + Binary_Lighting_Output_Timer(object_instance, milliseconds_elapsed); + zassert_equal(BLO_Blink.count, 0, "count=%u", BLO_Blink.count); + zassert_equal(BLO_Value.count, 0, "count=%u", BLO_Value.count); + zassert_equal(BLO_Value.pv, expect_pv, "value=%u", BLO_Value.pv); + + /* check WriteProperty properties */ + wpdata.object_type = OBJECT_BINARY_LIGHTING_OUTPUT; + wpdata.object_instance = object_instance; + wpdata.object_property = PROP_PRESENT_VALUE; + wpdata.priority = BACNET_MAX_PRIORITY; + wpdata.array_index = BACNET_ARRAY_ALL; + wpdata.error_class = ERROR_CLASS_PROPERTY; + wpdata.error_code = ERROR_CODE_SUCCESS; + /* ON */ + pv = BINARY_LIGHTING_PV_ON; + expect_pv = BINARY_LIGHTING_PV_ON; + wpdata.application_data_len = + encode_application_enumerated(wpdata.application_data, pv); + status = Binary_Lighting_Output_Write_Property(&wpdata); + zassert_true(status, NULL); + milliseconds = 2000; + milliseconds_elapsed = 100; + while (milliseconds) { + Binary_Lighting_Output_Timer(object_instance, milliseconds_elapsed); + test_pv = Binary_Lighting_Output_Present_Value(object_instance); + zassert_equal(expect_pv, test_pv, NULL); + test_priority = + Binary_Lighting_Output_Present_Value_Priority(object_instance); + zassert_equal(wpdata.priority, test_priority, + "priority=%u test_priority=%u", wpdata.priority, test_priority); + zassert_equal(BLO_Blink.count, 0, NULL); + zassert_equal(BLO_Value.count, 1, "count=%u", BLO_Value.count); + zassert_equal(BLO_Value.pv, expect_pv, NULL); + milliseconds -= milliseconds_elapsed; + } + /* OFF */ + pv = BINARY_LIGHTING_PV_OFF; + expect_pv = BINARY_LIGHTING_PV_OFF; + wpdata.application_data_len = + encode_application_enumerated(wpdata.application_data, pv); + status = Binary_Lighting_Output_Write_Property(&wpdata); + zassert_true(status, NULL); + milliseconds = 2000; + milliseconds_elapsed = 100; + while (milliseconds) { + Binary_Lighting_Output_Timer(object_instance, milliseconds_elapsed); + test_pv = Binary_Lighting_Output_Present_Value(object_instance); + zassert_equal(expect_pv, test_pv, NULL); + test_priority = + Binary_Lighting_Output_Present_Value_Priority(object_instance); + zassert_equal(wpdata.priority, test_priority, + "priority=%u test_priority=%u", wpdata.priority, test_priority); + zassert_equal(BLO_Blink.count, 0, NULL); + zassert_equal(BLO_Value.count, 2, "count=%u", BLO_Value.count); + zassert_equal(BLO_Value.pv, expect_pv, NULL); + milliseconds -= milliseconds_elapsed; + } + /* WARN - already off */ + pv = BINARY_LIGHTING_PV_WARN; + expect_pv = BINARY_LIGHTING_PV_OFF; + wpdata.application_data_len = + encode_application_enumerated(wpdata.application_data, pv); + status = Binary_Lighting_Output_Write_Property(&wpdata); + zassert_true(status, NULL); + milliseconds = 2000; + milliseconds_elapsed = 100; + while (milliseconds) { + Binary_Lighting_Output_Timer(object_instance, milliseconds_elapsed); + test_pv = Binary_Lighting_Output_Present_Value(object_instance); + zassert_equal(expect_pv, test_pv, "pv=%u", test_pv); + test_priority = + Binary_Lighting_Output_Present_Value_Priority(object_instance); + zassert_equal(wpdata.priority, test_priority, + "priority=%u test_priority=%u", wpdata.priority, test_priority); + zassert_equal(BLO_Blink.count, 0, NULL); + zassert_equal(BLO_Value.count, 2, "count=%u", BLO_Value.count); + zassert_equal(BLO_Value.pv, expect_pv, NULL); + milliseconds -= milliseconds_elapsed; + } +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(blo_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(blo_tests, ztest_unit_test(testBinaryLightingOutput), + ztest_unit_test(testBinaryLightingOutputBlink)); + + ztest_run_test_suite(blo_tests); +} +#endif diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index c287b70d..132cabfb 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -52,6 +52,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/object/ao.c ${SRC_DIR}/bacnet/basic/object/av.c ${SRC_DIR}/bacnet/basic/object/bi.c + ${SRC_DIR}/bacnet/basic/object/blo.c ${SRC_DIR}/bacnet/basic/object/bo.c ${SRC_DIR}/bacnet/basic/object/bv.c ${SRC_DIR}/bacnet/basic/object/channel.c