Feature/bacnet server client app example (#273)
* Create example server-client for R/W polling application example. Co-authored-by: Steve Karg <skarg@users.sourceforge.net>
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Steve Karg <skarg@users.sourceforge.net>
|
||||
* @date 2013
|
||||
* @brief Store properties from other BACnet devices
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
#include "bacnet/basic/sys/mstimer.h"
|
||||
/* us */
|
||||
#include "bacnet/basic/client/bac-rw.h"
|
||||
#include "bacnet/basic/client/bac-data.h"
|
||||
|
||||
/* number of objects data stored */
|
||||
#ifndef BACNET_DATA_OBJECT_MAX
|
||||
#define BACNET_DATA_OBJECT_MAX 16
|
||||
#endif
|
||||
/* Polling interval timer */
|
||||
static struct mstimer Object_Poll_Timer;
|
||||
/* property R/W process interval timer */
|
||||
static struct mstimer Read_Write_Timer;
|
||||
|
||||
/* variables for remote BACnet Object Data */
|
||||
typedef struct bacnet_object_data {
|
||||
uint32_t Device_ID;
|
||||
uint16_t Object_Type;
|
||||
uint32_t Object_ID;
|
||||
struct bacnet_present_value {
|
||||
/* application tag data type for writing */
|
||||
uint8_t tag;
|
||||
union {
|
||||
bool Boolean;
|
||||
float Real;
|
||||
uint32_t Unsigned_Int;
|
||||
int32_t Signed_Int;
|
||||
uint32_t Enumerated;
|
||||
} type;
|
||||
} Present_Value;
|
||||
bool refresh;
|
||||
} BACNET_DATA_OBJECT;
|
||||
static BACNET_DATA_OBJECT Object_Table[BACNET_DATA_OBJECT_MAX];
|
||||
|
||||
/**
|
||||
* @brief Find the index of a BACnet object type of a given instance.
|
||||
* @param device_instance - object-instance number of the device object
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @return The index of the object sought, or BACNET_STATUS_ERROR if
|
||||
* not found.
|
||||
*/
|
||||
static int bacnet_data_object_index_find(
|
||||
uint32_t device_instance, uint16_t object_type, uint32_t object_instance)
|
||||
{
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
unsigned i = 0;
|
||||
|
||||
for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) {
|
||||
object = &Object_Table[i];
|
||||
if ((object->Device_ID == device_instance) &&
|
||||
(object->Object_Type == object_type) &&
|
||||
(object->Object_ID == object_instance)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find a free index of an BACnet value object type
|
||||
* @return The index of a free element, or BACNET_STATUS_ERROR if
|
||||
* no free elements exist
|
||||
*/
|
||||
static int bacnet_data_object_index_find_free(void)
|
||||
{
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
unsigned i = 0;
|
||||
|
||||
for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) {
|
||||
object = &Object_Table[i];
|
||||
if ((object->Device_ID >= BACNET_MAX_INSTANCE) &&
|
||||
(object->Object_Type == MAX_BACNET_OBJECT_TYPE) &&
|
||||
(object->Object_ID >= BACNET_MAX_INSTANCE)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the BACnet object data
|
||||
*/
|
||||
static void bacnet_data_object_init(void)
|
||||
{
|
||||
unsigned i = 0;
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
|
||||
for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) {
|
||||
object = &Object_Table[i];
|
||||
object->Device_ID = BACNET_MAX_INSTANCE;
|
||||
object->Object_Type = MAX_BACNET_OBJECT_TYPE;
|
||||
object->Object_ID = BACNET_MAX_INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
static void bacnet_data_object_store(int index,
|
||||
BACNET_READ_PROPERTY_DATA *rp_data,
|
||||
BACNET_APPLICATION_DATA_VALUE *value)
|
||||
{
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
|
||||
assert(rp_data);
|
||||
assert(value);
|
||||
if ((index < BACNET_DATA_OBJECT_MAX) && (!value->context_specific)) {
|
||||
object = &Object_Table[index];
|
||||
switch (rp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value->tag == BACNET_APPLICATION_TAG_REAL) {
|
||||
object->Present_Value.tag = value->tag;
|
||||
object->Present_Value.type.Real = value->type.Real;
|
||||
}
|
||||
if (value->tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
|
||||
object->Present_Value.tag = value->tag;
|
||||
object->Present_Value.type.Unsigned_Int =
|
||||
value->type.Unsigned_Int;
|
||||
}
|
||||
if (value->tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
object->Present_Value.tag = value->tag;
|
||||
object->Present_Value.type.Enumerated =
|
||||
value->type.Enumerated;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
object->refresh = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the index of an analog value object type of a given instance.
|
||||
* @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.
|
||||
*/
|
||||
void bacnet_data_value_save(uint32_t device_instance,
|
||||
BACNET_READ_PROPERTY_DATA *rp_data,
|
||||
BACNET_APPLICATION_DATA_VALUE *value)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
if (!rp_data) {
|
||||
return;
|
||||
}
|
||||
if (rp_data->error_code != ERROR_CODE_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
switch (rp_data->object_type) {
|
||||
case OBJECT_ANALOG_INPUT:
|
||||
case OBJECT_ANALOG_OUTPUT:
|
||||
case OBJECT_ANALOG_VALUE:
|
||||
case OBJECT_BINARY_INPUT:
|
||||
case OBJECT_BINARY_OUTPUT:
|
||||
case OBJECT_BINARY_VALUE:
|
||||
case OBJECT_MULTI_STATE_INPUT:
|
||||
case OBJECT_MULTI_STATE_OUTPUT:
|
||||
case OBJECT_MULTI_STATE_VALUE:
|
||||
index = bacnet_data_object_index_find(device_instance,
|
||||
rp_data->object_type, rp_data->object_instance);
|
||||
if (index != BACNET_STATUS_ERROR) {
|
||||
bacnet_data_object_store(index, rp_data, value);
|
||||
}
|
||||
break;
|
||||
case OBJECT_DEVICE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the BACnet Data Analog Value processing
|
||||
* @param object - BACnet object structure data pointer
|
||||
*/
|
||||
static void bacnet_data_object_process(BACNET_DATA_OBJECT *object)
|
||||
{
|
||||
if (object && (object->Device_ID < BACNET_MAX_INSTANCE) &&
|
||||
(object->Object_ID < BACNET_MAX_INSTANCE)) {
|
||||
bacnet_read_property_queue(object->Device_ID,
|
||||
(BACNET_OBJECT_TYPE)object->Object_Type, object->Object_ID,
|
||||
PROP_PRESENT_VALUE, BACNET_ARRAY_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a BACnet Data remote value point
|
||||
* @param device_id - ID of the destination device
|
||||
* @param object_type - Type of the object whose property is to be read.
|
||||
* @param object_instance - Instance # of the object to be read.
|
||||
* @return true if added or existing, false if not added or existing
|
||||
*/
|
||||
bool bacnet_data_object_add(uint32_t device_id,
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance)
|
||||
{
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
bool status = false;
|
||||
int index = 0;
|
||||
|
||||
switch (object_type) {
|
||||
case OBJECT_ANALOG_INPUT:
|
||||
case OBJECT_ANALOG_OUTPUT:
|
||||
case OBJECT_ANALOG_VALUE:
|
||||
case OBJECT_BINARY_INPUT:
|
||||
case OBJECT_BINARY_OUTPUT:
|
||||
case OBJECT_BINARY_VALUE:
|
||||
case OBJECT_MULTI_STATE_INPUT:
|
||||
case OBJECT_MULTI_STATE_OUTPUT:
|
||||
case OBJECT_MULTI_STATE_VALUE:
|
||||
index = bacnet_data_object_index_find(
|
||||
device_id, object_type, object_instance);
|
||||
if (index == BACNET_STATUS_ERROR) {
|
||||
index = bacnet_data_object_index_find_free();
|
||||
if (index != BACNET_STATUS_ERROR) {
|
||||
object = &Object_Table[index];
|
||||
object->Device_ID = device_id;
|
||||
object->Object_Type = object_type;
|
||||
object->Object_ID = object_instance;
|
||||
object->refresh = true;
|
||||
status = true;
|
||||
}
|
||||
} else {
|
||||
object = &Object_Table[index];
|
||||
object->refresh = true;
|
||||
status = true;
|
||||
}
|
||||
break;
|
||||
case OBJECT_DEVICE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a Property value that has been stored
|
||||
* @param device_id - ID of the destination device
|
||||
* @param object_type - BACnet object type
|
||||
* @param object_instance - Instance # of the object to be read.
|
||||
* @param float_value [out] property value stored if available
|
||||
* @return true if found and value loaded
|
||||
*/
|
||||
bool bacnet_data_analog_present_value(uint32_t device_id,
|
||||
BACNET_OBJECT_TYPE object_type,
|
||||
uint32_t object_instance,
|
||||
float *float_value)
|
||||
{
|
||||
bool status = false;
|
||||
int index = 0;
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
|
||||
index =
|
||||
bacnet_data_object_index_find(device_id, object_type, object_instance);
|
||||
if (index == BACNET_STATUS_ERROR) {
|
||||
/* add to our object table if not found */
|
||||
bacnet_data_object_add(device_id, object_type, object_instance);
|
||||
} else {
|
||||
status = true;
|
||||
if (float_value) {
|
||||
object = &Object_Table[index];
|
||||
*float_value = object->Present_Value.type.Real;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a Property value that has been stored
|
||||
* @param device_id - ID of the destination device
|
||||
* @param object_type - BACnet object type
|
||||
* @param object_instance - Instance # of the object to be read.
|
||||
* @param bool_value [out] property value stored if available
|
||||
* @return true if found and value loaded
|
||||
*/
|
||||
bool bacnet_data_binary_present_value(uint32_t device_id,
|
||||
uint16_t object_type,
|
||||
uint32_t object_instance,
|
||||
bool *bool_value)
|
||||
{
|
||||
bool status = false;
|
||||
int index = 0;
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
|
||||
index =
|
||||
bacnet_data_object_index_find(device_id, object_type, object_instance);
|
||||
if (index == BACNET_STATUS_ERROR) {
|
||||
/* add to our object table if not found */
|
||||
bacnet_data_object_add(
|
||||
device_id, (BACNET_OBJECT_TYPE)object_type, object_instance);
|
||||
} else {
|
||||
status = true;
|
||||
if (bool_value) {
|
||||
object = &Object_Table[index];
|
||||
if (object->Present_Value.type.Enumerated == BINARY_INACTIVE) {
|
||||
*bool_value = false;
|
||||
} else {
|
||||
*bool_value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads a Property value that has been stored
|
||||
* @param device_id - ID of the destination device
|
||||
* @param object_type - BACnet object type
|
||||
* @param object_instance - Instance # of the object to be read.
|
||||
* @param bool_value [out] property value stored if available
|
||||
* @return true if found and value loaded
|
||||
*/
|
||||
bool bacnet_data_multistate_present_value(uint32_t device_id,
|
||||
uint16_t object_type,
|
||||
uint32_t object_instance,
|
||||
uint32_t *unsigned_value)
|
||||
{
|
||||
bool status = false;
|
||||
int index = 0;
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
|
||||
index =
|
||||
bacnet_data_object_index_find(device_id, object_type, object_instance);
|
||||
if (index == BACNET_STATUS_ERROR) {
|
||||
/* add to our object table if not found */
|
||||
bacnet_data_object_add(
|
||||
device_id, (BACNET_OBJECT_TYPE)object_type, object_instance);
|
||||
} else {
|
||||
status = true;
|
||||
if (unsigned_value) {
|
||||
object = &Object_Table[index];
|
||||
*unsigned_value = object->Present_Value.type.Unsigned_Int;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the BACnet Data repetitive task
|
||||
*/
|
||||
void bacnet_data_task(void)
|
||||
{
|
||||
static unsigned object_index = 0;
|
||||
BACNET_DATA_OBJECT *object = NULL;
|
||||
unsigned i = 0;
|
||||
|
||||
if (mstimer_expired(&Object_Poll_Timer)) {
|
||||
mstimer_reset(&Object_Poll_Timer);
|
||||
for (i = 0; i < BACNET_DATA_OBJECT_MAX; i++) {
|
||||
object = &Object_Table[i];
|
||||
object->refresh = true;
|
||||
}
|
||||
}
|
||||
if (mstimer_expired(&Read_Write_Timer)) {
|
||||
mstimer_reset(&Read_Write_Timer);
|
||||
bacnet_read_write_task();
|
||||
}
|
||||
if (bacnet_read_write_idle()) {
|
||||
object = &Object_Table[object_index];
|
||||
if (object->refresh) {
|
||||
object->refresh = false;
|
||||
bacnet_data_object_process(object);
|
||||
}
|
||||
object_index++;
|
||||
if (object_index >= BACNET_DATA_OBJECT_MAX) {
|
||||
object_index = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the BACnet Data Poll seconds
|
||||
* @param seconds - number of seconds between polling intervals
|
||||
*/
|
||||
void bacnet_data_poll_seconds_set(unsigned int seconds)
|
||||
{
|
||||
mstimer_set(&Object_Poll_Timer, seconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the BACnet Data Poll seconds
|
||||
* @return number of seconds between polling intervals
|
||||
*/
|
||||
unsigned int bacnet_data_poll_seconds(void)
|
||||
{
|
||||
return mstimer_interval(&Object_Poll_Timer) * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the ReadProperty module
|
||||
*/
|
||||
void bacnet_data_init(void)
|
||||
{
|
||||
bacnet_data_object_init();
|
||||
bacnet_read_write_init();
|
||||
/* start the cyclic poll timer */
|
||||
mstimer_set(&Object_Poll_Timer, 1 * 60 * 1000);
|
||||
mstimer_set(&Read_Write_Timer, 10);
|
||||
bacnet_read_write_value_callback_set(bacnet_data_value_save);
|
||||
}
|
||||
Reference in New Issue
Block a user