949 lines
33 KiB
C
949 lines
33 KiB
C
/**
|
|
* @file
|
|
* @brief A basic SubscribeCOV request handler, state machine, & task
|
|
* @author Steve Karg <skarg@users.sourceforge.net>
|
|
* @date 2007
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
/* 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;
|
|
}
|