cfe257fe69
* Fixed spelling errors detected by code-spell utility. * Fixed cppcheck preprocessorErrorDirective. Suppressed new cppcheck warnings until fixed.
1216 lines
40 KiB
C
1216 lines
40 KiB
C
/**
|
|
* @file
|
|
* @brief Discover all BACnet devices on a destination network
|
|
* @author Steve Karg <skarg@users.sourceforge.net>
|
|
* @date 2024
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
/* BACnet Stack defines - first */
|
|
#include "bacnet/bacdef.h"
|
|
/* BACnet Stack API */
|
|
#include "bacnet/bactext.h"
|
|
#include "bacnet/bacapp.h"
|
|
#include "bacnet/basic/sys/mstimer.h"
|
|
#include "bacnet/basic/sys/debug.h"
|
|
#include "bacnet/basic/sys/keylist.h"
|
|
#include "bacnet/basic/services.h"
|
|
#include "bacnet/property.h"
|
|
/* us */
|
|
#include "bacnet/basic/client/bac-rw.h"
|
|
#include "bacnet/basic/client/bac-discover.h"
|
|
|
|
/* send a Who-Is to discover new devices */
|
|
static struct mstimer WhoIs_Timer;
|
|
/* property R/W process interval timer */
|
|
static struct mstimer Read_Write_Timer;
|
|
/* list of devices */
|
|
static OS_Keylist Device_List = NULL;
|
|
/* discovery destination network */
|
|
static uint16_t Target_DNET = 0;
|
|
/* re-discovery time */
|
|
static unsigned long Discovery_Milliseconds;
|
|
/* states of discovery */
|
|
typedef enum bacnet_discover_state_enum {
|
|
BACNET_DISCOVER_STATE_INIT = 0,
|
|
BACNET_DISCOVER_STATE_BINDING,
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST,
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE,
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST,
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE,
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST,
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE,
|
|
BACNET_DISCOVER_STATE_OBJECT_NEXT,
|
|
BACNET_DISCOVER_STATE_DONE
|
|
} BACNET_DISCOVER_STATE;
|
|
|
|
typedef struct bacnet_property_data_t {
|
|
uint8_t *application_data;
|
|
int application_data_len;
|
|
} BACNET_PROPERTY_DATA;
|
|
|
|
typedef struct bacnet_object_data_t {
|
|
OS_Keylist Property_List;
|
|
/* used for discovering object data */
|
|
uint32_t Property_List_Size;
|
|
uint32_t Property_List_Index;
|
|
} BACNET_OBJECT_DATA;
|
|
|
|
typedef struct bacnet_device_data_t {
|
|
OS_Keylist Object_List;
|
|
/* used for discovering device data */
|
|
uint32_t Object_List_Size;
|
|
uint32_t Object_List_Index;
|
|
/* timer and stats */
|
|
struct mstimer Discovery_Timer;
|
|
unsigned long Discovery_Elapsed_Milliseconds;
|
|
BACNET_DISCOVER_STATE Discovery_State;
|
|
} BACNET_DEVICE_DATA;
|
|
|
|
/**
|
|
* @brief Add a ReadProperty reply data value to the property-list
|
|
* @param list - Keylist to add the property to
|
|
* @param key - BACnet property key
|
|
* @return Pointer to the property data structure
|
|
*/
|
|
static BACNET_PROPERTY_DATA *bacnet_property_data_add(OS_Keylist list, KEY key)
|
|
{
|
|
BACNET_PROPERTY_DATA *data = NULL;
|
|
int index;
|
|
|
|
data = Keylist_Data(list, key);
|
|
if (!data) {
|
|
data = calloc(1, sizeof(BACNET_PROPERTY_DATA));
|
|
if (data) {
|
|
index = Keylist_Data_Add(list, key, data);
|
|
if (index < 0) {
|
|
free(data);
|
|
data = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove all the property data from the property-list
|
|
* @param list - Keylist to remove the property from
|
|
*/
|
|
static void bacnet_property_data_cleanup(OS_Keylist list)
|
|
{
|
|
BACNET_PROPERTY_DATA *data = NULL;
|
|
|
|
do {
|
|
data = Keylist_Data_Pop(list);
|
|
if (data) {
|
|
free(data->application_data);
|
|
free(data);
|
|
}
|
|
} while (data);
|
|
Keylist_Delete(list);
|
|
}
|
|
|
|
/**
|
|
* @brief Add an object to the object list
|
|
* @param list - Keylist to add the object to
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - BACnet object instance
|
|
* @return Pointer to the object data structure
|
|
*/
|
|
static BACNET_OBJECT_DATA *bacnet_object_data_add(
|
|
OS_Keylist list, BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
|
|
{
|
|
BACNET_OBJECT_DATA *data = NULL;
|
|
KEY key;
|
|
int index;
|
|
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
data = Keylist_Data(list, key);
|
|
if (!data) {
|
|
data = calloc(1, sizeof(BACNET_OBJECT_DATA));
|
|
if (data) {
|
|
data->Property_List = Keylist_Create();
|
|
data->Property_List_Size = 0;
|
|
data->Property_List_Index = 0;
|
|
index = Keylist_Data_Add(list, key, data);
|
|
if (index < 0) {
|
|
free(data);
|
|
data = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief Add an object to the object list
|
|
* @param list - Keylist to add the object to
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - BACnet object instance
|
|
* @return Pointer to the object data structure
|
|
*/
|
|
static int bacnet_object_list_index(
|
|
OS_Keylist list, BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
|
|
{
|
|
KEY key;
|
|
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
return Keylist_Index(list, key);
|
|
}
|
|
|
|
/**
|
|
* @brief Remove all the property data from the object-list
|
|
* @param list - Keylist to remove the object and properties from
|
|
*/
|
|
static void bacnet_object_data_cleanup(OS_Keylist list)
|
|
{
|
|
BACNET_OBJECT_DATA *data = NULL;
|
|
|
|
do {
|
|
data = Keylist_Data_Pop(list);
|
|
if (data) {
|
|
bacnet_property_data_cleanup(data->Property_List);
|
|
free(data);
|
|
}
|
|
} while (data);
|
|
Keylist_Delete(list);
|
|
}
|
|
|
|
/**
|
|
* @brief Add a new device to the device list
|
|
* @param list - Keylist to add the device to
|
|
* @param device_instance - BACnet device instance
|
|
* @return Pointer to the device data structure only if new
|
|
*/
|
|
static BACNET_DEVICE_DATA *bacnet_device_data_add(uint32_t device_instance)
|
|
{
|
|
BACNET_DEVICE_DATA *data = NULL;
|
|
KEY key = device_instance;
|
|
int index;
|
|
|
|
if (Device_List) {
|
|
data = Keylist_Data(Device_List, key);
|
|
if (!data) {
|
|
/* device is not in the list */
|
|
data = calloc(1, sizeof(BACNET_DEVICE_DATA));
|
|
if (data) {
|
|
data->Object_List = Keylist_Create();
|
|
data->Discovery_State = BACNET_DISCOVER_STATE_INIT;
|
|
mstimer_set(&data->Discovery_Timer, 0);
|
|
/* other properties are already zeros */
|
|
/* add to list */
|
|
index = Keylist_Data_Add(Device_List, key, data);
|
|
if (index < 0) {
|
|
free(data);
|
|
data = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @brief Get an existing device from the device list
|
|
* @param list - Keylist to get the device from
|
|
* @param device_id - BACnet device instance
|
|
* @return Pointer to the device data structure
|
|
*/
|
|
static BACNET_DEVICE_DATA *
|
|
bacnet_device_data(OS_Keylist list, uint32_t device_id)
|
|
{
|
|
KEY key = device_id;
|
|
BACNET_DEVICE_DATA *device_data;
|
|
|
|
device_data = Keylist_Data(list, key);
|
|
|
|
return device_data;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove all the device data from the device-list
|
|
*/
|
|
void bacnet_discover_cleanup(void)
|
|
{
|
|
BACNET_DEVICE_DATA *data = NULL;
|
|
|
|
do {
|
|
data = Keylist_Data_Pop(Device_List);
|
|
if (data) {
|
|
bacnet_object_data_cleanup(data->Object_List);
|
|
free(data);
|
|
}
|
|
} while (data);
|
|
Keylist_Delete(Device_List);
|
|
}
|
|
|
|
/**
|
|
* @brief get the number of devices discovered
|
|
* @return the number of devices discovered
|
|
*/
|
|
int bacnet_discover_device_count(void)
|
|
{
|
|
int count;
|
|
|
|
count = Keylist_Count(Device_List);
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @brief get the device ID at a particular index
|
|
* @param index - 0..N of max devices
|
|
* @return the device ID at a particular index, or UINT32_MAX if not found
|
|
*/
|
|
uint32_t bacnet_discover_device_instance(unsigned index)
|
|
{
|
|
uint32_t instance = UINT32_MAX;
|
|
KEY key;
|
|
|
|
if (Keylist_Index_Key(Device_List, index, &key)) {
|
|
instance = key;
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* @brief get the number of objects discovered in a device
|
|
* @param device_id - ID of the destination device
|
|
* @return the number of objects discovered in a device
|
|
*/
|
|
int bacnet_discover_device_object_count(uint32_t device_id)
|
|
{
|
|
int count = 0;
|
|
BACNET_DEVICE_DATA *device;
|
|
KEY key = device_id;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
count = Keylist_Count(device->Object_List);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @brief get the number of objects discovered in a device
|
|
* @param device_id - ID of the destination device
|
|
* @param index - 0..N of max objects in a device
|
|
* @param object_id - object type and instance if object exists
|
|
* @return true if an object ID was found at this index
|
|
*/
|
|
bool bacnet_discover_device_object_identifier(
|
|
uint32_t device_id, unsigned index, BACNET_OBJECT_ID *object_id)
|
|
{
|
|
bool status = false;
|
|
BACNET_DEVICE_DATA *device;
|
|
KEY key = device_id;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
if (Keylist_Index_Key(device->Object_List, index, &key)) {
|
|
if (object_id) {
|
|
object_id->type = KEY_DECODE_TYPE(key);
|
|
object_id->instance = KEY_DECODE_ID(key);
|
|
}
|
|
status = true;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Determine the amount of heap data used by a device
|
|
* @param device_id - BACnet device instance
|
|
* @return the amount of heap data used by a device
|
|
*/
|
|
size_t bacnet_discover_device_memory(uint32_t device_id)
|
|
{
|
|
size_t heap_size = 0;
|
|
size_t object_count = 0, property_count = 0;
|
|
size_t i, j;
|
|
KEY key = device_id;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_DATA *object;
|
|
BACNET_PROPERTY_DATA *property;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
heap_size += sizeof(BACNET_DEVICE_DATA);
|
|
object_count = Keylist_Count(device->Object_List);
|
|
heap_size += (object_count * sizeof(BACNET_OBJECT_DATA));
|
|
for (i = 0; i < object_count; i++) {
|
|
object = Keylist_Data_Index(device->Object_List, i);
|
|
if (object) {
|
|
property_count = Keylist_Count(object->Property_List);
|
|
heap_size += (property_count * sizeof(BACNET_PROPERTY_DATA));
|
|
for (j = 0; j < property_count; j++) {
|
|
property = Keylist_Data_Index(object->Property_List, j);
|
|
if (property) {
|
|
heap_size += property->application_data_len;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return heap_size;
|
|
}
|
|
|
|
/**
|
|
* @brief get the elapsed time it took to discover a device
|
|
* @param device_id - ID of the destination device
|
|
* @return the elapsed time it took to discover a device
|
|
*/
|
|
unsigned long bacnet_discover_device_elapsed_milliseconds(uint32_t device_id)
|
|
{
|
|
unsigned long milliseconds = 0;
|
|
BACNET_DEVICE_DATA *device;
|
|
KEY key = device_id;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
milliseconds = device->Discovery_Elapsed_Milliseconds;
|
|
}
|
|
|
|
return milliseconds;
|
|
}
|
|
|
|
/**
|
|
* @brief Get a property value from the device cache
|
|
* @param device_id - ID of the destination device
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - Instance number of the object to be read.
|
|
* @param object_property - BACnet property identifier
|
|
* @param value property value stored if available (see tag for type)
|
|
* @return true if found and value loaded
|
|
*/
|
|
bool bacnet_discover_property_value(
|
|
uint32_t device_id,
|
|
BACNET_OBJECT_TYPE object_type,
|
|
uint32_t object_instance,
|
|
BACNET_PROPERTY_ID object_property,
|
|
BACNET_APPLICATION_DATA_VALUE *value)
|
|
{
|
|
bool status = false;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_DATA *object;
|
|
BACNET_PROPERTY_DATA *property;
|
|
KEY key = device_id;
|
|
int len = 0;
|
|
|
|
if (!value) {
|
|
return false;
|
|
}
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
object = Keylist_Data(device->Object_List, key);
|
|
if (object) {
|
|
key = object_property;
|
|
property = Keylist_Data(object->Property_List, key);
|
|
if (property) {
|
|
if (property->application_data_len > 0) {
|
|
len = bacapp_decode_known_property(
|
|
property->application_data,
|
|
property->application_data_len, value, object_type,
|
|
object_property);
|
|
if (len > 0) {
|
|
status = true;
|
|
}
|
|
} else {
|
|
bacapp_value_list_init(value, 1);
|
|
status = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Get a name property value from the device object property cache
|
|
* @param device_id - ID of the destination device
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - Instance number of the object to be read.
|
|
* @param object_property - BACnet property identifier
|
|
* @param buffer [out] Buffer to hold the property name.
|
|
* @param buffer_len [in] Length of the buffer.
|
|
* @param default_string [in] String to use if the property is not found.
|
|
* @return true if found and value copied, else false and default_string copied.
|
|
*/
|
|
bool bacnet_discover_property_name(
|
|
uint32_t device_id,
|
|
BACNET_OBJECT_TYPE object_type,
|
|
uint32_t object_instance,
|
|
BACNET_PROPERTY_ID object_property,
|
|
char *buffer,
|
|
size_t buffer_len,
|
|
const char *default_string)
|
|
{
|
|
BACNET_APPLICATION_DATA_VALUE value = { 0 };
|
|
bool status = false;
|
|
|
|
if (buffer && buffer_len) {
|
|
status = bacnet_discover_property_value(
|
|
device_id, object_type, object_instance, object_property, &value);
|
|
if (status && value.tag == BACNET_APPLICATION_TAG_CHARACTER_STRING) {
|
|
if (characterstring_valid(&value.type.Character_String)) {
|
|
strncpy(
|
|
buffer, characterstring_value(&value.type.Character_String),
|
|
buffer_len - 1);
|
|
} else {
|
|
status = false;
|
|
}
|
|
}
|
|
}
|
|
if (!status) {
|
|
strncpy(buffer, default_string, buffer_len);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the object property count from object property cache
|
|
* @param device_id - ID of the destination device
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - Instance number of the object to be read.
|
|
* @return number of object properties
|
|
*/
|
|
unsigned int bacnet_discover_object_property_count(
|
|
uint32_t device_id,
|
|
BACNET_OBJECT_TYPE object_type,
|
|
uint32_t object_instance)
|
|
{
|
|
unsigned int count = 0;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_DATA *object;
|
|
KEY key = device_id;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
object = Keylist_Data(device->Object_List, key);
|
|
if (object) {
|
|
count = Keylist_Count(object->Property_List);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @brief get the number of objects discovered in a device
|
|
* @param device_id - ID of the destination device
|
|
* @param object_type - BACnet object type
|
|
* @param object_instance - Instance number of the object to be queried
|
|
* @param index - 0..N of max properties in an object instance
|
|
* @param property_id - property identifier if object exists
|
|
* @return true if an object property ID was found at this index
|
|
*/
|
|
bool bacnet_discover_object_property_identifier(
|
|
uint32_t device_id,
|
|
BACNET_OBJECT_TYPE object_type,
|
|
uint32_t object_instance,
|
|
unsigned index,
|
|
uint32_t *property_id)
|
|
{
|
|
bool status = false;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_DATA *object;
|
|
KEY key = device_id;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (device) {
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
object = Keylist_Data(device->Object_List, key);
|
|
if (object) {
|
|
if (Keylist_Index_Key(object->Property_List, index, &key)) {
|
|
if (property_id) {
|
|
*property_id = key;
|
|
}
|
|
status = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief add a ReadProperty reply value from a device object property
|
|
* @param device_id [in] Device instance number where data originated
|
|
* @param rp_data [in] Pointer to the BACNET_READ_PROPERTY_DATA structure,
|
|
* which is packed with the information from the ReadProperty request.
|
|
* @param value [in] pointer to the BACNET_APPLICATION_DATA_VALUE structure
|
|
* which is packed with the decoded value from the ReadProperty request.
|
|
* @param device_data [in] Pointer to the device data structure
|
|
*/
|
|
static void bacnet_device_object_property_add(
|
|
uint32_t device_id,
|
|
const BACNET_READ_PROPERTY_DATA *rp_data,
|
|
const BACNET_APPLICATION_DATA_VALUE *value,
|
|
BACNET_DEVICE_DATA *device_data)
|
|
{
|
|
BACNET_OBJECT_DATA *object_data;
|
|
BACNET_PROPERTY_DATA *property_data;
|
|
|
|
if (!rp_data || !value || !device_data) {
|
|
return;
|
|
}
|
|
if ((rp_data->object_type == OBJECT_DEVICE) &&
|
|
(rp_data->object_instance == device_id) &&
|
|
(rp_data->object_property == PROP_OBJECT_LIST)) {
|
|
if (value->tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
|
|
device_data->Object_List_Size = value->type.Unsigned_Int;
|
|
device_data->Object_List_Index = 0;
|
|
if (device_data->Discovery_State ==
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE;
|
|
}
|
|
} else if (value->tag == BACNET_APPLICATION_TAG_OBJECT_ID) {
|
|
if (rp_data->array_index <= device_data->Object_List_Size) {
|
|
object_data = bacnet_object_data_add(
|
|
device_data->Object_List, value->type.Object_Id.type,
|
|
value->type.Object_Id.instance);
|
|
debug_printf(
|
|
"add %u object-list[%u] %s-%lu %s.\n", device_id,
|
|
device_data->Object_List_Index,
|
|
bactext_object_type_name(value->type.Object_Id.type),
|
|
(unsigned long)value->type.Object_Id.instance,
|
|
object_data ? "success" : "fail");
|
|
if (device_data->Discovery_State ==
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* move to next state */
|
|
if (device_data->Discovery_State ==
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE;
|
|
}
|
|
object_data = bacnet_object_data_add(
|
|
device_data->Object_List, rp_data->object_type,
|
|
rp_data->object_instance);
|
|
if (!object_data) {
|
|
debug_fprintf(
|
|
stderr, "%s-%u object fail to add!\n",
|
|
bactext_object_type_name(rp_data->object_type),
|
|
rp_data->object_instance);
|
|
return;
|
|
}
|
|
property_data = bacnet_property_data_add(
|
|
object_data->Property_List, rp_data->object_property);
|
|
if (!property_data) {
|
|
debug_fprintf(
|
|
stderr, "%s-%u %s property fail to add!\n",
|
|
bactext_object_type_name(rp_data->object_type),
|
|
rp_data->object_instance,
|
|
bactext_property_name(rp_data->object_property));
|
|
return;
|
|
}
|
|
if (rp_data->application_data_len > 0) {
|
|
if (property_data->application_data_len !=
|
|
rp_data->application_data_len) {
|
|
free(property_data->application_data);
|
|
property_data->application_data =
|
|
calloc(1, rp_data->application_data_len);
|
|
}
|
|
if (property_data->application_data) {
|
|
property_data->application_data_len =
|
|
rp_data->application_data_len;
|
|
memcpy(
|
|
property_data->application_data, rp_data->application_data,
|
|
rp_data->application_data_len);
|
|
} else {
|
|
debug_fprintf(
|
|
stderr, "%s-%u %s property fail to allocate!\n",
|
|
bactext_object_type_name(rp_data->object_type),
|
|
rp_data->object_instance,
|
|
bactext_property_name(rp_data->object_property));
|
|
}
|
|
} else {
|
|
free(property_data->application_data);
|
|
property_data->application_data = NULL;
|
|
property_data->application_data_len = 0;
|
|
}
|
|
if (rp_data->array_index == BACNET_ARRAY_ALL) {
|
|
debug_printf(
|
|
"%u object-list[%d] %s-%lu %s added.\n", device_id,
|
|
bacnet_object_list_index(
|
|
device_data->Object_List, rp_data->object_type,
|
|
rp_data->object_instance),
|
|
bactext_object_type_name(rp_data->object_type),
|
|
(unsigned long)rp_data->object_instance,
|
|
bactext_property_name(rp_data->object_property));
|
|
} else {
|
|
debug_printf(
|
|
"%u object-list[%d] %s-%lu %s[%lu] added.\n", device_id,
|
|
bacnet_object_list_index(
|
|
device_data->Object_List, rp_data->object_type,
|
|
rp_data->object_instance),
|
|
bactext_object_type_name(rp_data->object_type),
|
|
(unsigned long)rp_data->object_instance,
|
|
bactext_property_name(rp_data->object_property),
|
|
(unsigned long)rp_data->array_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the error from a ReadProperty or ReadPropertyMultiple
|
|
* @param device_id - device instance number where data originated
|
|
* @param rp_data - ReadProperty data structure
|
|
* @param device_data - Pointer to the device data structure
|
|
*/
|
|
static void Device_Error_Handler(
|
|
uint32_t device_id,
|
|
BACNET_READ_PROPERTY_DATA *rp_data,
|
|
BACNET_DEVICE_DATA *device_data)
|
|
{
|
|
bool status = false;
|
|
|
|
if (device_data) {
|
|
debug_printf(
|
|
"%u - %s\n", device_id,
|
|
bactext_error_code_name((int)rp_data->error_code));
|
|
switch (device_data->Discovery_State) {
|
|
case BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST:
|
|
/* resend request */
|
|
if (device_data->Object_List_Index != 0) {
|
|
device_data->Object_List_Index--;
|
|
}
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE;
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST:
|
|
if (rp_data->error_code == ERROR_CODE_TIMEOUT) {
|
|
/* resend request */
|
|
if (device_data->Object_List_Index != 0) {
|
|
device_data->Object_List_Index--;
|
|
}
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE;
|
|
} else if (
|
|
(rp_data->error_code ==
|
|
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED) &&
|
|
(rp_data->object_property == PROP_ALL)) {
|
|
/* fallback to ReadProperty required properties */
|
|
/* FIXME: fill a property-list with properties
|
|
and use FSM to ReadProperty of each.
|
|
For now, read the object-name property */
|
|
status = bacnet_read_property_queue(
|
|
device_id, rp_data->object_type,
|
|
rp_data->object_instance, PROP_OBJECT_NAME,
|
|
BACNET_ARRAY_ALL);
|
|
if (status) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST;
|
|
} else {
|
|
/* skip */
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE;
|
|
}
|
|
} else {
|
|
/* skip */
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Reply with the value from the ReadProperty request
|
|
* @param device_id [in] Device instance number
|
|
* @param rp_data [in] Pointer to the BACNET_READ_PROPERTY_DATA structure,
|
|
* which is packed with the information from the ReadProperty request.
|
|
* @param value [in] pointer to the BACNET_APPLICATION_DATA_VALUE structure
|
|
* which is packed with the decoded value from the ReadProperty request.
|
|
*/
|
|
static void bacnet_read_property_reply(
|
|
uint32_t device_id,
|
|
BACNET_READ_PROPERTY_DATA *rp_data,
|
|
BACNET_APPLICATION_DATA_VALUE *value)
|
|
{
|
|
BACNET_DEVICE_DATA *device_data;
|
|
|
|
if (!rp_data) {
|
|
return;
|
|
}
|
|
device_data = bacnet_device_data(Device_List, device_id);
|
|
if (!device_data) {
|
|
return;
|
|
}
|
|
if (rp_data->error_code != ERROR_CODE_SUCCESS) {
|
|
Device_Error_Handler(device_id, rp_data, device_data);
|
|
} else if (value) {
|
|
bacnet_device_object_property_add(
|
|
device_id, rp_data, value, device_data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Non-blocking task for running BACnet discover state machine
|
|
* @param device_id - Device ID from discovered device
|
|
* @param device_data - Pointer to the device data structure
|
|
*/
|
|
static void
|
|
bacnet_discover_device_fsm(uint32_t device_id, BACNET_DEVICE_DATA *device_data)
|
|
{
|
|
KEY key = 0;
|
|
BACNET_OBJECT_TYPE object_type = 0;
|
|
uint32_t object_instance = 0;
|
|
bool status = false;
|
|
|
|
if (!device_data) {
|
|
return;
|
|
}
|
|
switch (device_data->Discovery_State) {
|
|
case BACNET_DISCOVER_STATE_INIT:
|
|
status = bacnet_read_property_queue(
|
|
device_id, OBJECT_DEVICE, device_id, PROP_OBJECT_LIST, 0);
|
|
if (status) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST;
|
|
} else {
|
|
debug_fprintf(
|
|
stderr, "%u object-list-size fail to queue!\n", device_id);
|
|
}
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_REQUEST:
|
|
/* waiting for response */
|
|
return;
|
|
case BACNET_DISCOVER_STATE_OBJECT_LIST_SIZE_RESPONSE:
|
|
device_data->Object_List_Index = 0;
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE;
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST:
|
|
/* waiting for response */
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_LIST_RESPONSE:
|
|
device_data->Object_List_Index++;
|
|
if (device_data->Object_List_Index <=
|
|
device_data->Object_List_Size) {
|
|
debug_printf(
|
|
"%u object-list[%u] size=%u.\n", device_id,
|
|
device_data->Object_List_Index,
|
|
device_data->Object_List_Size);
|
|
status = bacnet_read_property_queue(
|
|
device_id, OBJECT_DEVICE, device_id, PROP_OBJECT_LIST,
|
|
device_data->Object_List_Index);
|
|
if (status) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_LIST_REQUEST;
|
|
return;
|
|
} else {
|
|
debug_fprintf(
|
|
stderr, "%u object-list[%u] %s-%u fail to queue!\n",
|
|
device_id, device_data->Object_List_Index,
|
|
bactext_object_type_name(object_type),
|
|
(unsigned)object_instance);
|
|
device_data->Object_List_Index--;
|
|
}
|
|
} else {
|
|
device_data->Object_List_Index = 0;
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE;
|
|
}
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST:
|
|
/* waiting for response */
|
|
break;
|
|
case BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_RESPONSE:
|
|
if (device_data->Object_List_Index <
|
|
device_data->Object_List_Size) {
|
|
if (Keylist_Index_Key(
|
|
device_data->Object_List,
|
|
device_data->Object_List_Index, &key)) {
|
|
object_type = KEY_DECODE_TYPE(key);
|
|
object_instance = KEY_DECODE_ID(key);
|
|
debug_printf(
|
|
"%u object-list[%u] %s-%u read ALL.\n", device_id,
|
|
device_data->Object_List_Index,
|
|
bactext_object_type_name(object_type),
|
|
(unsigned)object_instance);
|
|
status = bacnet_read_property_queue(
|
|
device_id, object_type, object_instance, PROP_ALL,
|
|
BACNET_ARRAY_ALL);
|
|
}
|
|
if (status) {
|
|
device_data->Discovery_State =
|
|
BACNET_DISCOVER_STATE_OBJECT_GET_PROPERTY_REQUEST;
|
|
device_data->Object_List_Index++;
|
|
} else {
|
|
debug_fprintf(
|
|
stderr, "%u object-list[%u] %s-%u fail to queue!\n",
|
|
device_id, device_data->Object_List_Index,
|
|
bactext_object_type_name(object_type),
|
|
(unsigned)object_instance);
|
|
}
|
|
} else {
|
|
/* track the duration */
|
|
device_data->Discovery_Elapsed_Milliseconds =
|
|
mstimer_elapsed(&device_data->Discovery_Timer);
|
|
/* rediscover in the future */
|
|
mstimer_set(
|
|
&device_data->Discovery_Timer, Discovery_Milliseconds);
|
|
device_data->Discovery_State = BACNET_DISCOVER_STATE_DONE;
|
|
}
|
|
break;
|
|
case BACNET_DISCOVER_STATE_DONE:
|
|
/* finished getting all the object properties */
|
|
if (mstimer_expired(&device_data->Discovery_Timer)) {
|
|
mstimer_set(&device_data->Discovery_Timer, 0);
|
|
device_data->Discovery_State = BACNET_DISCOVER_STATE_INIT;
|
|
}
|
|
break;
|
|
default:
|
|
debug_fprintf(
|
|
stderr, "%u unknown state %u!\n", device_id,
|
|
device_data->Discovery_State);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Adds a device to the device list
|
|
* @param device_id - Device ID from discovered device
|
|
* @param src - BACnet address from discovered device
|
|
* @return non-zero device index for device if added or existing
|
|
*/
|
|
static void bacnet_discover_devices_task(void)
|
|
{
|
|
unsigned int device_index = 0;
|
|
unsigned int device_count = 0;
|
|
uint32_t device_id = 0;
|
|
BACNET_DEVICE_DATA *device_data;
|
|
KEY key;
|
|
|
|
device_count = Keylist_Count(Device_List);
|
|
for (device_index = 0; device_index < device_count; device_index++) {
|
|
device_data = Keylist_Data_Index(Device_List, device_index);
|
|
if (!device_data) {
|
|
debug_fprintf(stderr, "device[%u] is NULL!\n", device_index);
|
|
continue;
|
|
}
|
|
if (Keylist_Index_Key(Device_List, device_index, &key)) {
|
|
device_id = key;
|
|
bacnet_discover_device_fsm(device_id, device_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Iterate a specific device object property list
|
|
* @param callback - function to call for each device object property
|
|
* @param context - pointer to user data
|
|
* @return true if the iteration completed, false if it stopped early
|
|
*/
|
|
bool bacnet_discover_device_object_property_iterate(
|
|
uint32_t device_id,
|
|
BACNET_OBJECT_TYPE object_type,
|
|
uint32_t object_instance,
|
|
bacnet_discover_device_callback callback,
|
|
void *context)
|
|
{
|
|
size_t property_count = 0;
|
|
size_t device_index = 0, object_index = 0, property_index = 0;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_DATA *object;
|
|
BACNET_PROPERTY_DATA *property;
|
|
BACNET_READ_PROPERTY_DATA rp_data = { 0 };
|
|
bool status = true;
|
|
KEY key = device_id;
|
|
|
|
/* device */
|
|
device = Keylist_Data(Device_List, key);
|
|
if (!device) {
|
|
return true;
|
|
}
|
|
device_index = Keylist_Index(Device_List, key);
|
|
/* object */
|
|
key = KEY_ENCODE(object_type, object_instance);
|
|
object = Keylist_Data(device->Object_List, key);
|
|
if (!object) {
|
|
return true;
|
|
}
|
|
object_index = Keylist_Index(device->Object_List, key);
|
|
rp_data.object_type = object_type;
|
|
rp_data.object_instance = object_instance;
|
|
/* property */
|
|
property_count = Keylist_Count(object->Property_List);
|
|
for (property_index = 0; property_index < property_count;
|
|
property_index++) {
|
|
if (Keylist_Index_Key(object->Property_List, property_index, &key)) {
|
|
rp_data.object_property = key;
|
|
} else {
|
|
continue;
|
|
}
|
|
property = Keylist_Data_Index(object->Property_List, property_index);
|
|
if (property) {
|
|
rp_data.error_class = ERROR_CLASS_PROPERTY;
|
|
rp_data.error_code = ERROR_CODE_SUCCESS;
|
|
rp_data.application_data = property->application_data;
|
|
rp_data.application_data_len = property->application_data_len;
|
|
status = callback(
|
|
device_id, device_index, object_index, property_index, &rp_data,
|
|
context);
|
|
/* callback returns true if the iteration
|
|
should continue, false if it should stop */
|
|
if (!status) {
|
|
return false;
|
|
}
|
|
} else {
|
|
rp_data.application_data = NULL;
|
|
rp_data.application_data_len = 0;
|
|
rp_data.error_class = ERROR_CLASS_PROPERTY;
|
|
rp_data.error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
|
status = callback(
|
|
device_id, device_index, object_index, property_index, &rp_data,
|
|
context);
|
|
/* callback returns true if the iteration
|
|
should continue, false if it should stop */
|
|
if (!status) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Iterate a specific device object list
|
|
* @param callback - function to call for each device object property
|
|
* @param context - pointer to user data
|
|
* @return true if the iteration completed, false if it stopped early
|
|
*/
|
|
bool bacnet_discover_device_object_iterate(
|
|
uint32_t device_id, bacnet_discover_device_callback callback, void *context)
|
|
{
|
|
size_t object_count = 0;
|
|
size_t object_index = 0;
|
|
BACNET_DEVICE_DATA *device;
|
|
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
|
|
uint32_t object_instance = 0;
|
|
KEY key = device_id;
|
|
bool status = false;
|
|
|
|
device = Keylist_Data(Device_List, key);
|
|
if (!device) {
|
|
return true;
|
|
}
|
|
object_count = Keylist_Count(device->Object_List);
|
|
for (object_index = 0; object_index < object_count; object_index++) {
|
|
if (Keylist_Index_Key(device->Object_List, object_index, &key)) {
|
|
object_type = KEY_DECODE_TYPE(key);
|
|
object_instance = KEY_DECODE_ID(key);
|
|
} else {
|
|
continue;
|
|
}
|
|
status = bacnet_discover_device_object_property_iterate(
|
|
device_id, object_type, object_instance, callback, context);
|
|
if (!status) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Iterate the device list
|
|
* @param callback - function to call for each device object property
|
|
* @param context - pointer to user data
|
|
* @return true if the iteration completed, false if it stopped early
|
|
*/
|
|
bool bacnet_discover_device_iterate(
|
|
bacnet_discover_device_callback callback, void *context)
|
|
{
|
|
size_t device_count = 0;
|
|
size_t device_index = 0;
|
|
uint32_t device_id = 0;
|
|
bool status = true;
|
|
KEY key = 0;
|
|
|
|
device_count = Keylist_Count(Device_List);
|
|
for (device_index = 0; device_index < device_count; device_index++) {
|
|
if (Keylist_Index_Key(Device_List, device_index, &key)) {
|
|
device_id = key;
|
|
status = bacnet_discover_device_object_iterate(
|
|
device_id, callback, context);
|
|
if (!status) {
|
|
return false;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Non-blocking task for running BACnet client tasks
|
|
* @param dest - BACnet address of the destination discovery
|
|
*/
|
|
void bacnet_discover_task(void)
|
|
{
|
|
BACNET_ADDRESS dest = { 0 };
|
|
|
|
if (mstimer_expired(&WhoIs_Timer)) {
|
|
mstimer_restart(&WhoIs_Timer);
|
|
dest.net = Target_DNET;
|
|
Send_WhoIs_To_Network(&dest, -1, -1);
|
|
}
|
|
if (mstimer_expired(&Read_Write_Timer)) {
|
|
mstimer_restart(&Read_Write_Timer);
|
|
bacnet_read_write_task();
|
|
}
|
|
if (bacnet_read_write_idle()) {
|
|
bacnet_discover_devices_task();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set the BACnet time between discovery in seconds
|
|
* @param seconds - number of seconds between discovery intervals
|
|
*/
|
|
void bacnet_discover_dnet_set(uint16_t dnet)
|
|
{
|
|
Target_DNET = dnet;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet time between discovery in seconds
|
|
* @return number of seconds between discovery intervals
|
|
*/
|
|
uint16_t bacnet_discover_dnet(void)
|
|
{
|
|
return Target_DNET;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets a Vendor ID filter on I-Am bindings to limit the address
|
|
* cache usage when we are only reading/writing to a specific vendor ID
|
|
* @param vendor_id - vendor ID to filter, 0=no filter
|
|
*/
|
|
void bacnet_discover_vendor_id_set(uint16_t vendor_id)
|
|
{
|
|
bacnet_read_write_vendor_id_filter_set(vendor_id);
|
|
}
|
|
|
|
/**
|
|
* @brief Gets a Vendor ID filter on I-Am bindings to limit the address
|
|
* cache usage when we are only reading/writing to a specific vendor ID
|
|
* @return vendor_id - vendor ID to filter, 0=no filter
|
|
*/
|
|
uint16_t bacnet_discover_vendor_id(void)
|
|
{
|
|
return bacnet_read_write_vendor_id_filter();
|
|
}
|
|
|
|
/**
|
|
* @brief Set the BACnet time between discovery in seconds
|
|
* @param seconds - number of seconds between discovery intervals
|
|
*/
|
|
void bacnet_discover_seconds_set(unsigned int seconds)
|
|
{
|
|
Discovery_Milliseconds = (unsigned long)seconds * 1000UL;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet time between discovery in seconds
|
|
* @return number of seconds between discovery intervals
|
|
*/
|
|
unsigned int bacnet_discover_seconds(void)
|
|
{
|
|
return Discovery_Milliseconds = 1000UL;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the millisecond timer for the read propcess (default=10ms)
|
|
* @param milliseconds - read process task time
|
|
*/
|
|
void bacnet_discover_read_process_milliseconds_set(unsigned long milliseconds)
|
|
{
|
|
mstimer_set(&Read_Write_Timer, milliseconds);
|
|
}
|
|
|
|
/**
|
|
* @brief Get the millisecond timer for the read propcess (default=10ms)
|
|
* @return read process task time in milliseconds
|
|
*/
|
|
unsigned long bacnet_discover_read_process_milliseconds(void)
|
|
{
|
|
return mstimer_interval(&Read_Write_Timer);
|
|
}
|
|
|
|
/**
|
|
* Save the I-Am service data to a data store
|
|
*
|
|
* @param device_instance [in] device instance number where data originated
|
|
* @param max_apdu [in] maximum APDU size
|
|
* @param segmentation [in] segmentation flag
|
|
* @param vendor_id [in] vendor identifier
|
|
*/
|
|
void bacnet_discover_device_add(
|
|
uint32_t device_instance,
|
|
unsigned max_apdu,
|
|
int segmentation,
|
|
uint16_t vendor_id)
|
|
{
|
|
BACNET_DEVICE_DATA *device_data;
|
|
|
|
(void)max_apdu;
|
|
(void)segmentation;
|
|
device_data = bacnet_device_data_add(device_instance);
|
|
debug_printf(
|
|
"device[%d] %lu - vendor=%u %s.\n",
|
|
Keylist_Index(Device_List, device_instance), device_instance, vendor_id,
|
|
device_data ? "success" : "fail");
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes the ReadProperty module
|
|
*/
|
|
void bacnet_discover_init(void)
|
|
{
|
|
Device_List = Keylist_Create();
|
|
bacnet_read_write_init();
|
|
/* default value in case it is not set */
|
|
if (!mstimer_interval(&WhoIs_Timer)) {
|
|
mstimer_set(&WhoIs_Timer, 5UL * 60UL * 1000UL);
|
|
}
|
|
/* force WhoIs_Timer to be expired to send WhoIs immediately */
|
|
WhoIs_Timer.start = mstimer_now() - WhoIs_Timer.interval;
|
|
/* default value in case it is not set */
|
|
if (!mstimer_interval(&Read_Write_Timer)) {
|
|
mstimer_set(&Read_Write_Timer, 10);
|
|
}
|
|
bacnet_read_write_value_callback_set(bacnet_read_property_reply);
|
|
bacnet_read_write_device_callback_set(bacnet_discover_device_add);
|
|
}
|