/** * @file * @brief A basic SubscribeCOV request handler, state machine, & task * @author Steve Karg * @date 2007 * @copyright SPDX-License-Identifier: MIT */ #include #include #include #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" /* BACnet Stack API */ #include "bacnet/bacerror.h" #include "bacnet/bacdcode.h" #include "bacnet/bacaddr.h" #include "bacnet/apdu.h" #include "bacnet/npdu.h" #include "bacnet/abort.h" #include "bacnet/reject.h" #include "bacnet/cov.h" #include "bacnet/dcc.h" #if PRINT_ENABLED #include "bacnet/bactext.h" #endif /* basic objects, services, TSM, and datalink */ #include "bacnet/basic/tsm/tsm.h" #include "bacnet/basic/object/device.h" #include "bacnet/basic/services.h" #include "bacnet/basic/sys/keylist.h" #include "bacnet/basic/sys/debug.h" #include "bacnet/datalink/datalink.h" #ifndef MAX_COV_PROPERTIES #define MAX_COV_PROPERTIES 2 #endif typedef struct BACnet_COV_Handler_Address { bool valid : 1; BACNET_ADDRESS dest; } BACNET_COV_HANDLER_ADDRESS; /* note: This COV service only monitors the properties of an object that have been specified in the standard. */ typedef struct BACnet_COV_Handler_Subscription_Flags { bool issueConfirmedNotifications : 1; /* optional */ bool send_requested : 1; } BACNET_COV_HANDLER_SUBSCRIPTION_FLAGS; typedef struct BACnet_COV_Handler_Subscription { BACNET_COV_HANDLER_SUBSCRIPTION_FLAGS flag; unsigned dest_index; uint8_t invokeID; /* for confirmed COV */ uint32_t subscriberProcessIdentifier; uint32_t lifetime; /* optional */ BACNET_OBJECT_ID monitoredObjectIdentifier; } BACNET_COV_HANDLER_SUBSCRIPTION; #ifndef MAX_COV_SUBSCRIPTIONS #define MAX_COV_SUBSCRIPTIONS 128 #endif static OS_Keylist COV_Subscriptions_List[MAX_NUM_DEVICES]; #ifdef BAC_ROUTING #define COV_Subscriptions (COV_Subscriptions_List[Routed_Device_Object_Index()]) #else #define COV_Subscriptions (COV_Subscriptions_List[0]) #endif #ifndef MAX_COV_ADDRESSES #define MAX_COV_ADDRESSES 16 #endif static BACNET_COV_HANDLER_ADDRESS COV_Addresses[MAX_COV_ADDRESSES]; /** * @brief Deletes a subscription * @param list_idx - keylist index * @return true if the subscription was deleted */ static bool cov_subscription_delete(uint32_t list_idx) { bool status = false; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; subscription = Keylist_Data_Delete_By_Index(COV_Subscriptions, list_idx); if (subscription) { free(subscription); status = true; } return status; } /** * @brief Creates a subscription * @param list_key - keylist key * @return the subscription that was created, or NULL */ static BACNET_COV_HANDLER_SUBSCRIPTION * cov_subscription_create(uint32_t list_key) { BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; int index = 0; if (list_key >= MAX_COV_SUBSCRIPTIONS) { return NULL; } subscription = Keylist_Data(COV_Subscriptions, list_key); if (!subscription) { subscription = calloc(1, sizeof(BACNET_COV_HANDLER_SUBSCRIPTION)); if (subscription) { index = Keylist_Data_Add(COV_Subscriptions, list_key, subscription); if (index < 0) { free(subscription); return NULL; } } else { return NULL; } } return subscription; } /** * Gets the address from the list of COV addresses * * @param index - offset into COV address list where address is stored * @param dest - address to be filled when found * * @return true if valid address, false if not valid or not found */ static BACNET_ADDRESS *cov_address_get(unsigned index) { BACNET_ADDRESS *cov_dest = NULL; if (index < MAX_COV_ADDRESSES) { if (COV_Addresses[index].valid) { cov_dest = &COV_Addresses[index].dest; } } return cov_dest; } /** * Removes the address from the list of COV addresses, if it is not * used by other COV subscriptions */ static void cov_address_remove_unused(void) { unsigned index = 0; unsigned cov_index = 0; bool found = false; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; #ifdef BAC_ROUTING uint16_t current_dev_id = Routed_Device_Object_Index(); uint16_t dev_id = 0; for (cov_index = 0; cov_index < MAX_COV_ADDRESSES; cov_index++) { if (COV_Addresses[cov_index].valid) { found = false; for (dev_id = 0; dev_id < Get_Num_Managed_Devices(); dev_id++) { Set_Routed_Device_Object_Index(dev_id); for (index = 0; index < Keylist_Count(COV_Subscriptions); index++) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (subscription && (subscription->dest_index == cov_index)) { found = true; break; } } if (found) { break; } } if (!found) { COV_Addresses[cov_index].valid = false; } } } Set_Routed_Device_Object_Index(current_dev_id); #else for (cov_index = 0; cov_index < MAX_COV_ADDRESSES; cov_index++) { if (COV_Addresses[cov_index].valid) { found = false; for (index = 0; index < Keylist_Count(COV_Subscriptions); index++) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (subscription && (subscription->dest_index == cov_index)) { found = true; break; } } if (!found) { COV_Addresses[cov_index].valid = false; } } } #endif } /** * Adds the address to the list of COV addresses * * @param dest - address to be added if there is room in the list * * @return index number 0..N, or -1 if unable to add */ static int cov_address_add(const BACNET_ADDRESS *dest) { int index = -1; unsigned i = 0; bool found = false; bool valid = false; BACNET_ADDRESS *cov_dest = NULL; if (dest) { for (i = 0; i < MAX_COV_ADDRESSES; i++) { valid = COV_Addresses[i].valid; if (valid) { cov_dest = &COV_Addresses[i].dest; found = bacnet_address_same(dest, cov_dest); if (found) { index = i; break; } } } if (!found) { /* find a free place to add a new address */ for (i = 0; i < MAX_COV_ADDRESSES; i++) { valid = COV_Addresses[i].valid; if (!valid) { index = i; cov_dest = &COV_Addresses[i].dest; bacnet_address_copy(cov_dest, dest); COV_Addresses[i].valid = true; break; } } } } return index; } /** Handle a request to list all the COV subscriptions. * @ingroup DSCOV * Invoked by a request to read the Device object's * PROP_ACTIVE_COV_SUBSCRIPTIONS. Loops through the list of COV Subscriptions, * and, for each valid one, adds its description to the APDU. * @param apdu [out] Buffer in which the APDU contents are built. * @param apdu_size [in] Max length of the APDU buffer. * @return Number of bytes encoded in the buffer, or * #BACNET_STATUS_ABORT if the response would not fit within the buffer. */ int handler_cov_encode_subscriptions(uint8_t *apdu, int apdu_size) { BACNET_COV_SUBSCRIPTION data = { 0 }; BACNET_ADDRESS *dest = NULL; int len = 0; int apdu_len = 0; unsigned index = 0; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; for (index = 0; index < Keylist_Count(COV_Subscriptions); index++) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (!subscription) { continue; } dest = cov_address_get(subscription->dest_index); if (!dest) { continue; } data.time_remaining = subscription->lifetime; data.cov_increment_present = false; data.cov_increment = 1.0f; /* dummy value */ data.issue_confirmed_notifications = subscription->flag.issueConfirmedNotifications; data.monitored_property_reference.object_identifier.type = subscription->monitoredObjectIdentifier.type; data.monitored_property_reference.object_identifier.instance = subscription->monitoredObjectIdentifier.instance; data.monitored_property_reference.property_identifier = PROP_PRESENT_VALUE; data.monitored_property_reference.property_array_index = BACNET_ARRAY_ALL; data.recipient.process_identifier = subscription->subscriberProcessIdentifier; data.recipient.recipient.tag = BACNET_RECIPIENT_TAG_ADDRESS; bacnet_address_copy(&data.recipient.recipient.type.address, dest); len = bacnet_cov_subscription_encode(apdu, apdu_size - apdu_len, &data); if (len <= 0) { return BACNET_STATUS_ABORT; } apdu_len += len; if (apdu) { apdu += len; } } return apdu_len; } /** Handler to initialize the COV list, clearing and disabling each entry. * @ingroup DSCOV */ void handler_cov_init(void) { unsigned index = 0; #ifdef BAC_ROUTING uint16_t current_dev_id = Routed_Device_Object_Index(); uint16_t dev_id = 0; for (dev_id = 0; dev_id < MAX_NUM_DEVICES; dev_id++) { Set_Routed_Device_Object_Index(dev_id); if (!COV_Subscriptions) { COV_Subscriptions = Keylist_Create(); } else { Keylist_Data_Free(COV_Subscriptions); } } Set_Routed_Device_Object_Index(current_dev_id); #else if (!COV_Subscriptions) { COV_Subscriptions = Keylist_Create(); } else { Keylist_Data_Free(COV_Subscriptions); } #endif for (index = 0; index < MAX_COV_ADDRESSES; index++) { COV_Addresses[index].valid = false; } } static bool cov_list_subscribe( const BACNET_ADDRESS *src, const BACNET_SUBSCRIBE_COV_DATA *cov_data, BACNET_ERROR_CLASS *error_class, BACNET_ERROR_CODE *error_code) { bool existing_entry = false; int index; bool found = true; bool address_match = false; const BACNET_ADDRESS *dest = NULL; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; /* unable to subscribe - resources? */ /* unable to cancel subscription - other? */ /* existing? - match Object ID and Process ID and address */ for (index = 0; index < Keylist_Count(COV_Subscriptions); index++) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (subscription) { dest = cov_address_get(subscription->dest_index); if (dest) { address_match = bacnet_address_same(src, dest); } else { /* skip address matching - we don't have an address */ address_match = true; } if ((subscription->monitoredObjectIdentifier.type == cov_data->monitoredObjectIdentifier.type) && (subscription->monitoredObjectIdentifier.instance == cov_data->monitoredObjectIdentifier.instance) && (subscription->subscriberProcessIdentifier == cov_data->subscriberProcessIdentifier) && address_match) { existing_entry = true; if (subscription->invokeID) { tsm_free_invoke_id(subscription->invokeID); subscription->invokeID = 0; } if (cov_data->cancellationRequest) { cov_subscription_delete(index); cov_address_remove_unused(); } else { subscription->dest_index = cov_address_add(src); subscription->flag.issueConfirmedNotifications = cov_data->issueConfirmedNotifications; subscription->lifetime = cov_data->lifetime; subscription->flag.send_requested = true; } break; } } } if (!existing_entry && (Keylist_Count(COV_Subscriptions) < MAX_COV_SUBSCRIPTIONS) && (!cov_data->cancellationRequest)) { const int addr_add_ret = cov_address_add(src); if (addr_add_ret < 0) { *error_class = ERROR_CLASS_RESOURCES; *error_code = ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT; found = false; } else { found = true; index = Keylist_Next_Empty_Key(COV_Subscriptions, 0); subscription = cov_subscription_create(index); if (subscription) { subscription->dest_index = addr_add_ret; subscription->monitoredObjectIdentifier.type = cov_data->monitoredObjectIdentifier.type; subscription->monitoredObjectIdentifier.instance = cov_data->monitoredObjectIdentifier.instance; subscription->subscriberProcessIdentifier = cov_data->subscriberProcessIdentifier; subscription->flag.issueConfirmedNotifications = cov_data->issueConfirmedNotifications; subscription->invokeID = 0; subscription->lifetime = cov_data->lifetime; subscription->flag.send_requested = true; } else { *error_class = ERROR_CLASS_RESOURCES; *error_code = ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT; found = false; } } } else if (!existing_entry) { if (Keylist_Count(COV_Subscriptions) >= MAX_COV_SUBSCRIPTIONS) { /* Out of resources */ *error_class = ERROR_CLASS_RESOURCES; *error_code = ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT; found = false; } else { /* cancellationRequest - valid object not subscribed */ /* From BACnet Standard 135-2010-13.14.2 ...Cancellations that are issued for which no matching COV context can be found shall succeed as if a context had existed, returning 'Result(+)'. */ found = true; } } return found; } static bool cov_send_request( BACNET_COV_HANDLER_SUBSCRIPTION *cov_subscription, BACNET_PROPERTY_VALUE *value_list) { int len = 0; int pdu_len = 0; BACNET_NPDU_DATA npdu_data = { 0 }; BACNET_ADDRESS my_address = { 0 }; int bytes_sent = 0; uint8_t invoke_id = 0; bool status = false; /* return value */ BACNET_COV_DATA cov_data = { 0 }; BACNET_ADDRESS *dest = NULL; if (!dcc_communication_enabled()) { return status; } #if PRINT_ENABLED debug_fprintf(stderr, "COVnotification: requested\n"); #endif if (!cov_subscription) { return status; } dest = cov_address_get(cov_subscription->dest_index); if (!dest) { #if PRINT_ENABLED debug_fprintf(stderr, "COVnotification: dest not found!\n"); #endif return status; } datalink_get_my_address(&my_address); npdu_encode_npdu_data( &npdu_data, cov_subscription->flag.issueConfirmedNotifications, MESSAGE_PRIORITY_NORMAL); pdu_len = npdu_encode_pdu( &Handler_Transmit_Buffer[0], dest, &my_address, &npdu_data); /* load the COV data structure for outgoing message */ cov_data.subscriberProcessIdentifier = cov_subscription->subscriberProcessIdentifier; cov_data.initiatingDeviceIdentifier = Device_Object_Instance_Number(); cov_data.monitoredObjectIdentifier.type = cov_subscription->monitoredObjectIdentifier.type; cov_data.monitoredObjectIdentifier.instance = cov_subscription->monitoredObjectIdentifier.instance; cov_data.timeRemaining = cov_subscription->lifetime; cov_data.listOfValues = value_list; if (cov_subscription->flag.issueConfirmedNotifications) { invoke_id = tsm_next_free_invokeID(); if (invoke_id) { cov_subscription->invokeID = invoke_id; len = ccov_notify_encode_apdu( &Handler_Transmit_Buffer[pdu_len], sizeof(Handler_Transmit_Buffer) - pdu_len, invoke_id, &cov_data); } else { goto COV_FAILED; } } else { len = ucov_notify_encode_apdu( &Handler_Transmit_Buffer[pdu_len], sizeof(Handler_Transmit_Buffer) - pdu_len, &cov_data); } pdu_len += len; if (cov_subscription->flag.issueConfirmedNotifications) { tsm_set_confirmed_unsegmented_transaction( invoke_id, dest, &npdu_data, &Handler_Transmit_Buffer[0], (uint16_t)pdu_len); } bytes_sent = datalink_send_pdu( dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); if (bytes_sent > 0) { status = true; #if PRINT_ENABLED debug_fprintf(stderr, "COVnotification: Sent!\n"); #endif } COV_FAILED: return status; } static void cov_lifetime_expiration_handler( unsigned index, uint32_t elapsed_seconds, uint32_t lifetime_seconds) { if (index < MAX_COV_SUBSCRIPTIONS) { BACNET_COV_HANDLER_SUBSCRIPTION *subscription = Keylist_Data_Index(COV_Subscriptions, index); /* handle lifetime expiration */ if (lifetime_seconds >= elapsed_seconds) { subscription->lifetime -= elapsed_seconds; #if 0 fprintf(stderr, "COVtimer: subscription[%d].lifetime=%lu\n", index, (unsigned long) subscription->lifetime); #endif } else { subscription->lifetime = 0; } if (subscription->lifetime == 0) { /* expire the subscription */ #if PRINT_ENABLED debug_fprintf( stderr, "COVtimer: PID=%u %s %u time remaining=%u seconds\n", subscription->subscriberProcessIdentifier, bactext_object_type_name( subscription->monitoredObjectIdentifier.type), subscription->monitoredObjectIdentifier.instance, subscription->lifetime); #endif if (subscription->flag.issueConfirmedNotifications) { if (subscription->invokeID) { tsm_free_invoke_id(subscription->invokeID); subscription->invokeID = 0; } } cov_subscription_delete(index); cov_address_remove_unused(); } } } /** Handler to check the list of subscribed objects for any that have * changed and so need to have notifications sent. * @ingroup DSCOV * This handler will be invoked by the main program every second or so. * This example only handles Binary Inputs, but can be easily extended to * support other types. * For each subscribed object, * - See if the subscription has timed out * - Remove it if it has timed out. * - See if the subscribed object instance has changed * (eg, check with Binary_Input_Change_Of_Value() ) * - If changed, * - Clear the COV (eg, Binary_Input_Change_Of_Value_Clear() ) * - Send the notice with cov_send_request() * - Will be confirmed or unconfirmed, as per the subscription. * * @note worst case tasking: MS/TP with the ability to send only * one notification per task cycle. * * @param elapsed_seconds [in] How many seconds have elapsed since last * called. */ void handler_cov_timer_seconds(uint32_t elapsed_seconds) { int index = 0; uint32_t lifetime_seconds = 0; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = NULL; int list_cnt = 0; #ifdef BAC_ROUTING uint16_t current_dev_id = Routed_Device_Object_Index(); uint16_t dev_id = 0; if (elapsed_seconds) { /* handle the subscription timeouts */ for (dev_id = 0; dev_id < Get_Num_Managed_Devices(); dev_id++) { Set_Routed_Device_Object_Index(dev_id); list_cnt = Keylist_Count(COV_Subscriptions); /* Iterate in reverse order due to subscription deletion */ for (index = list_cnt - 1; index >= 0; index--) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (subscription) { lifetime_seconds = subscription->lifetime; if (lifetime_seconds) { /* only expire COV with definite lifetimes */ cov_lifetime_expiration_handler( index, elapsed_seconds, lifetime_seconds); } } } } } Set_Routed_Device_Object_Index(current_dev_id); #else if (elapsed_seconds) { /* handle the subscription timeouts */ list_cnt = Keylist_Count(COV_Subscriptions); /* Iterate in reverse order due to subscription deletion */ for (index = list_cnt - 1; index >= 0; index--) { subscription = Keylist_Data_Index(COV_Subscriptions, index); if (subscription) { lifetime_seconds = subscription->lifetime; if (lifetime_seconds) { /* only expire COV with definite lifetimes */ cov_lifetime_expiration_handler( index, elapsed_seconds, lifetime_seconds); } } } } #endif } bool handler_cov_fsm(void) { static int indices[MAX_NUM_DEVICES] = { 0 }; #ifdef BAC_ROUTING const int dev_id = Routed_Device_Object_Index(); #else const int dev_id = 0; #endif BACNET_OBJECT_TYPE object_type = MAX_BACNET_OBJECT_TYPE; uint32_t object_instance = 0; bool status = false; bool send = false; BACNET_PROPERTY_VALUE value_list[MAX_COV_PROPERTIES] = { 0 }; /* states for transmitting */ typedef enum cov_fsm_state { COV_STATE_IDLE = 0, COV_STATE_MARK, COV_STATE_CLEAR, COV_STATE_FREE, COV_STATE_SEND } cov_fsm_state_t; static cov_fsm_state_t cov_task_states[MAX_NUM_DEVICES] = { COV_STATE_IDLE }; int index = indices[dev_id]; cov_fsm_state_t cov_task_state = cov_task_states[dev_id]; BACNET_COV_HANDLER_SUBSCRIPTION *subscription = Keylist_Data_Index(COV_Subscriptions, index); int list_cnt = Keylist_Count(COV_Subscriptions); switch (cov_task_state) { case COV_STATE_IDLE: if (subscription) { cov_task_state = COV_STATE_MARK; } else { index++; if (index >= list_cnt) { index = 0; } } break; case COV_STATE_MARK: /* mark any subscriptions where the value has changed */ if (subscription) { object_type = (BACNET_OBJECT_TYPE) subscription->monitoredObjectIdentifier.type; object_instance = subscription->monitoredObjectIdentifier.instance; status = Device_COV(object_type, object_instance); if (status) { subscription->flag.send_requested = true; #if PRINT_ENABLED debug_fprintf(stderr, "COVtask: Marking...\n"); #endif } } index++; if (index >= list_cnt) { index = 0; cov_task_state = COV_STATE_CLEAR; } break; case COV_STATE_CLEAR: /* clear the COV flag after checking all subscriptions */ if (subscription && (subscription->flag.send_requested)) { object_type = (BACNET_OBJECT_TYPE) subscription->monitoredObjectIdentifier.type; object_instance = subscription->monitoredObjectIdentifier.instance; Device_COV_Clear(object_type, object_instance); } index++; if (index >= list_cnt) { index = 0; cov_task_state = COV_STATE_FREE; } break; case COV_STATE_FREE: /* confirmed notification house keeping */ if (subscription && (subscription->flag.issueConfirmedNotifications) && (subscription->invokeID)) { if (tsm_invoke_id_free(subscription->invokeID)) { subscription->invokeID = 0; } else if (tsm_invoke_id_failed(subscription->invokeID)) { tsm_free_invoke_id(subscription->invokeID); subscription->invokeID = 0; } } index++; if (index >= list_cnt) { index = 0; cov_task_state = COV_STATE_SEND; } break; case COV_STATE_SEND: /* send any COVs that are requested */ if (subscription && (subscription->flag.send_requested)) { send = true; if (subscription->flag.issueConfirmedNotifications) { if (subscription->invokeID != 0) { /* already sending */ send = false; } if (!tsm_transaction_available()) { /* no transactions available - can't send now */ send = false; } } if (send) { object_type = (BACNET_OBJECT_TYPE) subscription->monitoredObjectIdentifier.type; object_instance = subscription->monitoredObjectIdentifier.instance; #if PRINT_ENABLED debug_fprintf(stderr, "COVtask: Sending...\n"); #endif /* configure the linked list for the two properties */ bacapp_property_value_list_init( &value_list[0], MAX_COV_PROPERTIES); status = Device_Encode_Value_List( object_type, object_instance, &value_list[0]); if (status) { status = cov_send_request(subscription, &value_list[0]); } if (status) { subscription->flag.send_requested = false; } } } index++; if (index >= list_cnt) { index = 0; cov_task_state = COV_STATE_IDLE; } break; default: index = 0; cov_task_state = COV_STATE_IDLE; break; } indices[dev_id] = index; cov_task_states[dev_id] = cov_task_state; return (cov_task_state == COV_STATE_IDLE); } void handler_cov_task(void) { #ifdef BAC_ROUTING uint16_t current_dev_id = Routed_Device_Object_Index(); uint16_t dev_id = 0; for (dev_id = 0; dev_id < Get_Num_Managed_Devices(); dev_id++) { Set_Routed_Device_Object_Index(dev_id); handler_cov_fsm(); } Set_Routed_Device_Object_Index(current_dev_id); #else handler_cov_fsm(); #endif } static bool cov_subscribe( const BACNET_ADDRESS *src, const BACNET_SUBSCRIBE_COV_DATA *cov_data, BACNET_ERROR_CLASS *error_class, BACNET_ERROR_CODE *error_code) { bool status = false; /* return value */ BACNET_OBJECT_TYPE object_type = MAX_BACNET_OBJECT_TYPE; uint32_t object_instance = 0; object_type = (BACNET_OBJECT_TYPE)cov_data->monitoredObjectIdentifier.type; object_instance = cov_data->monitoredObjectIdentifier.instance; status = Device_Valid_Object_Id(object_type, object_instance); if (status) { status = Device_Value_List_Supported(object_type); if (status) { status = cov_list_subscribe(src, cov_data, error_class, error_code); } else if (cov_data->cancellationRequest) { /* From BACnet Standard 135-2010-13.14.2 ...Cancellations that are issued for which no matching COV context can be found shall succeed as if a context had existed, returning 'Result(+)'. */ status = true; } else { *error_class = ERROR_CLASS_OBJECT; *error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; } } else if (cov_data->cancellationRequest) { /* From BACnet Standard 135-2010-13.14.2 ...Cancellations that are issued for which no matching COV context can be found shall succeed as if a context had existed, returning 'Result(+)'. */ status = true; } else { *error_class = ERROR_CLASS_OBJECT; *error_code = ERROR_CODE_UNKNOWN_OBJECT; } return status; } /** Handler for a COV Subscribe Service request. * @ingroup DSCOV * This handler will be invoked by apdu_handler() if it has been enabled * by a call to apdu_set_confirmed_handler(). * This handler builds a response packet, which is * - an Abort if * - the message is segmented * - if decoding fails * - an ACK, if cov_subscribe() succeeds * - an Error if cov_subscribe() fails * * @param service_request [in] The contents of the service request. * @param service_len [in] The length of the service_request. * @param src [in] BACNET_ADDRESS of the source of the message * @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information * decoded from the APDU header of this message. */ void handler_cov_subscribe( uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src, BACNET_CONFIRMED_SERVICE_DATA *service_data) { BACNET_SUBSCRIBE_COV_DATA cov_data = { 0 }; int len = 0; int pdu_len = 0; int npdu_len = 0; int apdu_len = 0; BACNET_NPDU_DATA npdu_data = { 0 }; bool success = false; int bytes_sent = 0; BACNET_ADDRESS my_address = { 0 }; bool error = false; /* Has the COV Initialization been called? */ if (!COV_Subscriptions) { handler_cov_init(); } /* initialize a common abort code */ cov_data.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; /* encode the NPDU portion of the packet */ datalink_get_my_address(&my_address); npdu_encode_npdu_data(&npdu_data, false, service_data->priority); npdu_len = npdu_encode_pdu( &Handler_Transmit_Buffer[0], src, &my_address, &npdu_data); if (service_len == 0) { len = BACNET_STATUS_REJECT; cov_data.error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; debug_print("CCOV: Missing Required Parameter. Sending Reject!\n"); error = true; } else if (service_data->segmented_message) { /* we don't support segmentation - send an abort */ len = BACNET_STATUS_ABORT; debug_print("SubscribeCOV: Segmented message. Sending Abort!\n"); error = true; } else { len = cov_subscribe_decode_service_request( service_request, service_len, &cov_data); if (len <= 0) { debug_print("SubscribeCOV: Unable to decode Request!\n"); } if (len < 0) { error = true; } else { cov_data.error_class = ERROR_CLASS_OBJECT; cov_data.error_code = ERROR_CODE_UNKNOWN_OBJECT; success = cov_subscribe( src, &cov_data, &cov_data.error_class, &cov_data.error_code); if (success) { apdu_len = encode_simple_ack( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, SERVICE_CONFIRMED_SUBSCRIBE_COV); debug_print("SubscribeCOV: Sending Simple Ack!\n"); } else { len = BACNET_STATUS_ERROR; error = true; debug_print("SubscribeCOV: Sending Error!\n"); } } } /* Error? */ if (error) { if (len == BACNET_STATUS_ABORT) { apdu_len = abort_encode_apdu( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, abort_convert_error_code(cov_data.error_code), true); debug_print("SubscribeCOV: Sending Abort!\n"); } else if (len == BACNET_STATUS_ERROR) { apdu_len = bacerror_encode_apdu( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, SERVICE_CONFIRMED_SUBSCRIBE_COV, cov_data.error_class, cov_data.error_code); debug_print("SubscribeCOV: Sending Error!\n"); } else if (len == BACNET_STATUS_REJECT) { apdu_len = reject_encode_apdu( &Handler_Transmit_Buffer[npdu_len], service_data->invoke_id, reject_convert_error_code(cov_data.error_code)); debug_print("SubscribeCOV: Sending Reject!\n"); } } pdu_len = npdu_len + apdu_len; bytes_sent = datalink_send_pdu( src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); if (bytes_sent <= 0) { debug_perror("SubscribeCOV: Failed to send PDU"); } return; }