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:
Steve Karg
2022-05-16 15:02:17 -05:00
committed by GitHub
parent 1c1b676247
commit ba0cbc1fb8
10 changed files with 1923 additions and 11 deletions
+13 -10
View File
@@ -29,6 +29,11 @@ option(
"compile the piface app"
OFF)
option(
BACNET_BUILD_BACPOLL_APP
"compile the bacpoll app"
ON)
option(
BACDL_ETHERNET
"compile with ethernet support"
@@ -558,16 +563,14 @@ if(BACNET_STACK_BUILD_APPS)
target_link_libraries(piface PRIVATE ${PROJECT_NAME})
endif(BACNET_BUILD_PIFACE_APP)
# add_executable(
# ptransfer
# apps/ptransfer/main.c
# apps/ptransfer/h_pt_a.h
# apps/ptransfer/h_pt_a.c
# apps/ptransfer/h_pt.h
# apps/ptransfer/h_pt.c
# apps/ptransfer/s_ptransfer.h
# apps/ptransfer/s_ptransfer.c)
# target_link_libraries(ptransfer PRIVATE ${PROJECT_NAME})
if(BACNET_BUILD_BACPOLL_APP)
add_executable(bacpoll
apps/server-client/main.c
src/bacnet/basic/client/bac-task.c
src/bacnet/basic/client/bac-data.c
src/bacnet/basic/client/bac-rw.c)
target_link_libraries(bacpoll PRIVATE ${PROJECT_NAME})
endif(BACNET_BUILD_BACPOLL_APP)
if(NOT BACDL_ETHERNET)
add_executable(readbdt apps/readbdt/main.c)
+6 -1
View File
@@ -135,7 +135,8 @@ endif
SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \
whohas whois iam ucov scov timesync epics readpropm readrange \
writepropm uptransfer getevent uevent abort error event ack-alarm
writepropm uptransfer getevent uevent abort error event ack-alarm \
server-client
ifeq (${BACDL_DEFINE},-DBACDL_BIP=1)
SUBDIRS += whoisrouter iamrouter initrouter
@@ -260,6 +261,10 @@ scov: $(BACNET_LIB_TARGET)
server: $(BACNET_LIB_TARGET)
$(MAKE) -s -b -C $@
.PHONY: server-client
server-client: $(BACNET_LIB_TARGET)
$(MAKE) -s -b -C $@
.PHONY: timesync
timesync: $(BACNET_LIB_TARGET)
$(MAKE) -b -C $@
+45
View File
@@ -0,0 +1,45 @@
#Makefile to build BACnet Application using GCC compiler
# Executable file name - BACnet Server-Client Polling Application
TARGET = bacpoll
# BACnet objects that are used with this app
# BACnet objects that are used with this app
BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object
BACNET_CLIENT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/client
SRC = main.c \
$(BACNET_OBJECT_DIR)/client/device-client.c \
$(BACNET_OBJECT_DIR)/netport.c \
$(BACNET_CLIENT_DIR)/bac-data.c \
$(BACNET_CLIENT_DIR)/bac-rw.c \
$(BACNET_CLIENT_DIR)/bac-task.c
# TARGET_EXT is defined in apps/Makefile as .exe or nothing
TARGET_BIN = ${TARGET}$(TARGET_EXT)
OBJS += ${SRC:.c=.o}
all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN}
${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET}
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
size $@
cp $@ ../../bin
${BACNET_LIB_TARGET}:
( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s )
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
.PHONY: depend
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
.PHONY: clean
clean:
rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET}
.PHONY: include
include: .depend
+227
View File
@@ -0,0 +1,227 @@
/**
* @file
* @author Steve Karg
* @date 2022
* @brief Application to acquire data from a target client
*
* SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include "bacnet/config.h"
#include "bacnet/bacdef.h"
#include "bacnet/bacenum.h"
#include "bacnet/bactext.h"
#include "bacnet/version.h"
#include "bacnet/basic/sys/filename.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/client/bac-task.h"
#include "bacnet/basic/client/bac-data.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/datalink/dlenv.h"
/* print with flush by default */
#define PRINTF(...) \
fprintf(stderr, __VA_ARGS__); \
fflush(stderr)
/* current version of the BACnet stack */
static const char *BACnet_Version = BACNET_VERSION_TEXT;
static void print_usage(const char *filename)
{
PRINTF("Usage: %s [device-instance]\n", filename);
PRINTF(" [object-type] [object-instance]\n");
PRINTF(" [--device][--print-seconds]\n");
PRINTF(" [--version][--help]\n");
}
static void print_help(const char *filename)
{
PRINTF("Simulate a BACnet server-client device.\n");
PRINTF("device-instance:\n"
"BACnet Device Object Instance number that you are\n"
"trying to communicate to. This number will be used\n"
"to try and bind with the device using Who-Is and\n"
"I-Am services. For example, if you were reading\n"
"Device Object 123, the device-instance would be 123.\n"
"\nobject-type:\n"
"The object type is object that you are reading. It\n"
"can be defined either as the object-type name string\n"
"as defined in the BACnet specification, or as the\n"
"integer value of the enumeration BACNET_OBJECT_TYPE\n"
"in bacenum.h. For example if you were reading Analog\n"
"Output 2, the object-type would be analog-output or 1.\n"
"\nobject-instance:\n"
"This is the object instance number of the object that\n"
"you are reading. For example, if you were reading\n"
"Analog Output 2, the object-instance would be 2.\n");
PRINTF("\n"
"Example:\n"
"If you want read the Present-Value of Analog Output 101\n"
"in Device 123, you could send either of the following\n"
"commands:\n"
"%s 123 analog-output 101\n"
"%s 123 1 101\n"
"If you want read the Present-Value of Binary Input 1\n"
"in Device 123, you could send either of the following\n"
"commands:\n"
"%s 123 binary-input 1\n"
"%s 123 3 1\n",
filename, filename, filename, filename);
}
/**
* @brief Main function of server-client demo.
* @param argc [in] Arg count.
* @param argv [in] Takes one argument: the Device Instance #.
* @return 0 on success.
*/
int main(int argc, char *argv[])
{
int argi = 0;
unsigned int target_args = 0;
const char *filename = NULL;
uint32_t device_id = BACNET_MAX_INSTANCE;
struct mstimer print_value_timer = { 0 };
float float_value = 0.0;
bool bool_value = false;
uint32_t unsigned_value = 0;
/* data from the command line */
unsigned long print_seconds = 10;
uint32_t target_device_object_instance = BACNET_MAX_INSTANCE;
uint32_t target_object_instance = BACNET_MAX_INSTANCE;
BACNET_OBJECT_TYPE target_object_type = OBJECT_ANALOG_INPUT;
filename = filename_remove_path(argv[0]);
for (argi = 1; argi < argc; argi++) {
if (strcmp(argv[argi], "--help") == 0) {
print_usage(filename);
print_help(filename);
return 0;
}
if (strcmp(argv[argi], "--version") == 0) {
PRINTF("%s %s\n", filename, BACNET_VERSION_TEXT);
PRINTF("Copyright (C) 2022 by Steve Karg and others.\n"
"This is free software; see the source for copying "
"conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or\n"
"FITNESS FOR A PARTICULAR PURPOSE.\n");
return 0;
}
if (strcmp(argv[argi], "--device") == 0) {
if (++argi < argc) {
device_id = strtol(argv[argi], NULL, 0);
if (device_id > BACNET_MAX_INSTANCE) {
fprintf(stderr, "device=%u - it must be less than %u\n",
device_id, BACNET_MAX_INSTANCE);
return 1;
}
}
} else if (strcmp(argv[argi], "--print-seconds") == 0) {
if (++argi < argc) {
print_seconds = strtol(argv[argi], NULL, 0);
}
} else {
if (target_args == 0) {
target_device_object_instance = strtol(argv[argi], NULL, 0);
target_args++;
} else if (target_args == 1) {
if (bactext_object_type_strtol(
argv[argi], &target_object_type) == false) {
fprintf(stderr, "object-type=%s invalid\n", argv[argi]);
return 1;
}
target_args++;
} else if (target_args == 2) {
target_object_instance = strtol(argv[argi], NULL, 0);
target_args++;
} else {
print_usage(filename);
return 1;
}
}
}
if (target_args < 2) {
print_usage(filename);
return 0;
}
Device_Set_Object_Instance_Number(device_id);
if (target_device_object_instance > BACNET_MAX_INSTANCE) {
fprintf(stderr, "device-instance=%u - it must be less than %u\n",
target_device_object_instance, BACNET_MAX_INSTANCE);
return 1;
}
PRINTF("BACnet Server-Client Demo\n"
"BACnet Stack Version %s\n"
"BACnet Device ID: %u\n"
"Max APDU: %d\n",
BACnet_Version, Device_Object_Instance_Number(), MAX_APDU);
fflush(stdout);
dlenv_init();
atexit(datalink_cleanup);
bacnet_task_init();
bacnet_data_poll_seconds_set(print_seconds);
if (!bacnet_data_object_add(target_device_object_instance,
target_object_type, target_object_instance)) {
return 1;
}
mstimer_set(&print_value_timer, print_seconds * 1000);
/* loop forever */
for (;;) {
bacnet_task();
if (mstimer_expired(&print_value_timer)) {
mstimer_reset(&print_value_timer);
switch (target_object_type) {
case OBJECT_ANALOG_INPUT:
case OBJECT_ANALOG_OUTPUT:
case OBJECT_ANALOG_VALUE:
if (bacnet_data_analog_present_value(
target_device_object_instance, target_object_type,
target_object_instance, &float_value)) {
PRINTF("Device %u %s-%u=%f\n",
(unsigned)target_device_object_instance,
bactext_object_type_name(target_object_type),
(unsigned)target_object_instance, float_value);
}
break;
case OBJECT_BINARY_INPUT:
case OBJECT_BINARY_OUTPUT:
case OBJECT_BINARY_VALUE:
if (bacnet_data_binary_present_value(
target_device_object_instance, target_object_type,
target_object_instance, &bool_value)) {
PRINTF("Device %u %s-%u=%s\n",
(unsigned)target_device_object_instance,
bactext_object_type_name(target_object_type),
(unsigned)target_object_instance,
bool_value ? "active" : "inactive");
}
break;
case OBJECT_MULTI_STATE_INPUT:
case OBJECT_MULTI_STATE_OUTPUT:
case OBJECT_MULTI_STATE_VALUE:
if (bacnet_data_multistate_present_value(
target_device_object_instance, target_object_type,
target_object_instance, &unsigned_value)) {
PRINTF("Device %u %s-%u=%u\n",
(unsigned)target_device_object_instance,
bactext_object_type_name(target_object_type),
(unsigned)target_object_instance,
(unsigned)unsigned_value);
}
break;
default:
return 1;
break;
}
}
}
return 0;
}
+420
View File
@@ -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);
}
+64
View File
@@ -0,0 +1,64 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2013
*
* SPDX-License-Identifier: MIT
*/
#ifndef BAC_DATA_H
#define BAC_DATA_H
#include <stdint.h>
#include "bacnet/bacdef.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacapp.h"
#include "bacnet/rp.h"
#include "bacnet/bacnet_stack_exports.h"
struct bacnet_status_flags_t {
bool in_alarm : 1;
bool fault : 1;
bool overridden : 1;
bool out_of_service : 1;
};
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
void bacnet_data_init(void);
BACNET_STACK_EXPORT
void bacnet_data_task(void);
BACNET_STACK_EXPORT
void bacnet_data_poll_seconds_set(unsigned int seconds);
BACNET_STACK_EXPORT
unsigned int bacnet_data_poll_seconds(void);
BACNET_STACK_EXPORT
void bacnet_data_value_save(uint32_t device_instance,
BACNET_READ_PROPERTY_DATA *rp_data,
BACNET_APPLICATION_DATA_VALUE *value);
BACNET_STACK_EXPORT
bool bacnet_data_object_add(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance);
BACNET_STACK_EXPORT
bool bacnet_data_analog_present_value(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
float *float_value);
BACNET_STACK_EXPORT
bool bacnet_data_multistate_present_value(uint32_t device_id,
uint16_t object_type,
uint32_t object_instance,
uint32_t *unsigned_value);
BACNET_STACK_EXPORT
bool bacnet_data_binary_present_value(uint32_t device_id,
uint16_t object_type,
uint32_t object_instance,
bool *bool_value);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+908
View File
@@ -0,0 +1,908 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2013
* @brief Read properties from other BACnet devices, and store their values
*
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "bacnet/abort.h"
#include "bacnet/apdu.h"
#include "bacnet/iam.h"
#include "bacnet/reject.h"
#include "bacnet/rp.h"
#include "bacnet/wp.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/basic/binding/address.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/object/device.h"
#include "bacnet/basic/sys/ringbuf.h"
#include "bacnet/basic/tsm/tsm.h"
/* me */
#include "bacnet/basic/client/bac-rw.h"
/* timer for address cache */
static struct mstimer Cache_Timer;
#define CACHE_CYCLE_SECONDS 60
/* timeout timer for read-write task */
static struct mstimer Read_Write_Timer;
/* where the data from the read is stored */
static bacnet_read_write_value_callback_t bacnet_read_write_value_callback;
/* states for client task */
typedef enum {
BACNET_CLIENT_IDLE,
BACNET_CLIENT_BIND,
BACNET_CLIENT_BINDING,
BACNET_CLIENT_SEND,
BACNET_CLIENT_WAITING,
BACNET_CLIENT_FINISHED
} BACNET_CLIENT_STATE;
/* data queue */
typedef struct target_data_t {
bool write_property;
uint32_t device_id;
uint32_t object_instance;
BACNET_OBJECT_TYPE object_type;
BACNET_PROPERTY_ID object_property;
int32_t array_index;
uint8_t priority;
/* application tag data type for writing */
uint8_t tag;
union {
/* only supporting limited values for writing */
bool Boolean;
float Real;
uint32_t Enumerated;
uint32_t Unsigned_Int;
int32_t Signed_Int;
} type;
} TARGET_DATA;
#define TARGET_DATA_QUEUE_SIZE (sizeof(struct target_data_t))
/* count must be a power of 2 for ringbuf library */
#ifndef TARGET_DATA_QUEUE_COUNT
#define TARGET_DATA_QUEUE_COUNT 8
#endif
static TARGET_DATA Target_Data_Buffer[TARGET_DATA_QUEUE_COUNT];
static RING_BUFFER Target_Data_Queue;
/* local storage - keeps it off the c-stack */
static BACNET_APPLICATION_DATA_VALUE Target_Decoded_Property_Value;
/* the invoke id is needed to filter incoming messages */
static uint8_t Request_Invoke_ID;
static BACNET_ADDRESS Target_Address;
static uint32_t Target_Device_ID;
static uint16_t Target_Vendor_ID;
static bool Error_Detected = false;
static BACNET_ERROR_CLASS Error_Class;
static BACNET_ERROR_CODE Error_Code;
static BACNET_CLIENT_STATE RW_State = BACNET_CLIENT_IDLE;
/**
* @brief Handler for an Error PDU.
* @param src [in] BACNET_ADDRESS of the source of the message
* @param invoke_id [in] the invokeID from the rejected message
* @param error_class [in] the error class
* @param error_code [in] the error code
*/
static void MyErrorHandler(BACNET_ADDRESS *src,
uint8_t invoke_id,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code)
{
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
Error_Detected = true;
Error_Class = error_class;
Error_Code = error_code;
}
}
/**
* @brief Handler for an Abort PDU.
* @param src [in] BACNET_ADDRESS of the source of the message
* @param invoke_id [in] the invokeID from the rejected message
* @param abort_reason [in] the reason for the message abort
* @param server
*/
static void MyAbortHandler(
BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server)
{
(void)server;
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = abort_convert_to_error_code(abort_reason);
}
}
/**
* @brief Handler for a Reject PDU.
* @param src [in] BACNET_ADDRESS of the source of the message
* @param invoke_id [in] the invokeID from the rejected message
* @param reject_reason [in] the reason for the rejection
*/
static void MyRejectHandler(
BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason)
{
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = reject_convert_to_error_code(reject_reason);
}
}
/**
* @brief Handler for I-Am responses
* @param service_request [in] The received message to be handled.
* @param service_len [in] Length of the service_request message.
* @param src [in] The BACNET_ADDRESS of the message's source.
*/
static void My_I_Am_Bind(
uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src)
{
int len = 0;
uint32_t device_id = 0;
unsigned max_apdu = 0;
int segmentation = 0;
uint16_t vendor_id = 0;
bool found = false;
bool bind = false;
(void)service_len;
len = iam_decode_service_request(
service_request, &device_id, &max_apdu, &segmentation, &vendor_id);
if (len > 0) {
found = address_bind_request(device_id, NULL, NULL);
if (!found) {
if (Target_Vendor_ID != 0) {
if (Target_Vendor_ID == vendor_id) {
/* limit binding to specific vendor ID */
bind = true;
}
} else {
bind = true;
}
if (bind) {
address_add_binding(device_id, max_apdu, src);
}
}
}
return;
}
/** Handler for a Simple ACK PDU.
*
* @param src [in] BACNET_ADDRESS of the source of the message
* @param invoke_id [in] the invokeID from the rejected message
*/
static void MyWritePropertySimpleAckHandler(
BACNET_ADDRESS *src, uint8_t invoke_id)
{
if (address_match(&Target_Address, src) &&
(invoke_id == Request_Invoke_ID)) {
/* nothing to do */
}
}
/** Handler for a ReadProperty ACK.
* Saves the data from a matching read-property request
*
* @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.
*/
static void My_Read_Property_Ack_Handler(uint8_t *service_request,
uint16_t service_len,
BACNET_ADDRESS *src,
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
int len = 0;
BACNET_READ_PROPERTY_DATA rp_data;
uint8_t *application_data;
int application_data_len;
BACNET_APPLICATION_DATA_VALUE *value;
uint32_t device_id = 0;
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
len = rp_ack_decode_service_request(
service_request, service_len, &rp_data);
if (len < 0) {
/* unable to decode value */
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = ERROR_CODE_INTERNAL_ERROR;
} else {
address_get_device_id(src, &device_id);
application_data = rp_data.application_data;
application_data_len = rp_data.application_data_len;
/* value? need to loop until all of the len is gone... */
for (;;) {
value = &Target_Decoded_Property_Value;
len = bacapp_decode_application_data(
application_data, (uint8_t)application_data_len, value);
if (len > 0) {
/* handle the data */
rp_data.error_class = ERROR_CLASS_SERVICES;
rp_data.error_code = ERROR_CODE_SUCCESS;
if (bacnet_read_write_value_callback) {
bacnet_read_write_value_callback(
device_id, &rp_data, value);
}
/* see if there is any more data */
if (len < application_data_len) {
application_data += len;
application_data_len -= len;
} else {
break;
}
} else {
break;
}
}
}
}
}
static void bacnet_rpm_process(
uint32_t device_id, BACNET_READ_ACCESS_DATA *rpm_data)
{
BACNET_PROPERTY_REFERENCE *listOfProperties;
BACNET_APPLICATION_DATA_VALUE *value;
BACNET_READ_PROPERTY_DATA rp_data;
if (rpm_data) {
rp_data.error_class = ERROR_CLASS_SERVICES;
rp_data.error_code = ERROR_CODE_SUCCESS;
rp_data.object_type = rpm_data->object_type;
rp_data.object_instance = rpm_data->object_instance;
listOfProperties = rpm_data->listOfProperties;
while (listOfProperties) {
rp_data.object_property = listOfProperties->propertyIdentifier;
rp_data.array_index = listOfProperties->propertyArrayIndex;
if (listOfProperties->propertyArrayIndex == BACNET_ARRAY_ALL) {
rp_data.array_index = 1;
}
value = listOfProperties->value;
while (value) {
if (bacnet_read_write_value_callback) {
bacnet_read_write_value_callback(
device_id, &rp_data, value);
}
value = value->next;
if (listOfProperties->propertyArrayIndex == BACNET_ARRAY_ALL) {
rp_data.array_index++;
}
}
listOfProperties = listOfProperties->next;
}
}
}
/** Handler for a ReadPropertyMultiple ACK.
* Saves the data from a matching read-property-multiple request
*
* @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.
*/
static void My_Read_Property_Multiple_Ack_Handler(uint8_t *service_request,
uint16_t service_len,
BACNET_ADDRESS *src,
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
int len = 0;
BACNET_READ_ACCESS_DATA *rpm_data;
BACNET_READ_ACCESS_DATA *old_rpm_data;
BACNET_PROPERTY_REFERENCE *rpm_property;
BACNET_PROPERTY_REFERENCE *old_rpm_property;
BACNET_APPLICATION_DATA_VALUE *value;
BACNET_APPLICATION_DATA_VALUE *old_value;
uint32_t device_id = 0;
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
rpm_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
if (rpm_data) {
len = rpm_ack_decode_service_request(
service_request, service_len, rpm_data);
}
if (len > 0) {
address_get_device_id(src, &device_id);
while (rpm_data) {
rpm_ack_print_data(rpm_data);
bacnet_rpm_process(device_id, rpm_data);
rpm_property = rpm_data->listOfProperties;
while (rpm_property) {
value = rpm_property->value;
while (value) {
old_value = value;
value = value->next;
free(old_value);
}
old_rpm_property = rpm_property;
rpm_property = rpm_property->next;
free(old_rpm_property);
}
old_rpm_data = rpm_data;
rpm_data = rpm_data->next;
free(old_rpm_data);
}
} else {
while (rpm_data) {
rpm_property = rpm_data->listOfProperties;
while (rpm_property) {
value = rpm_property->value;
while (value) {
old_value = value;
value = value->next;
free(old_value);
}
old_rpm_property = rpm_property;
rpm_property = rpm_property->next;
free(old_rpm_property);
}
old_rpm_data = rpm_data;
rpm_data = rpm_data->next;
free(old_rpm_data);
}
}
}
}
/**
* @brief Sends a ReadPropertyMultiple service request
* @param device_id [in] The contents of the service request.
* @param object_type [in] The contents of the service request.
* @param object_instance [in] The contents of the service request.
* @return invoke_id of request
*/
static uint8_t Send_RPM_All_Request(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance)
{
BACNET_READ_ACCESS_DATA read_access_data = { 0 };
BACNET_PROPERTY_REFERENCE property_list = { 0 };
uint8_t pdu[MAX_PDU] = { 0 };
/* configure the property list */
property_list.error.error_class = ERROR_CLASS_DEVICE;
property_list.error.error_code = ERROR_CODE_OTHER;
property_list.value = NULL;
property_list.propertyArrayIndex = BACNET_ARRAY_ALL;
property_list.propertyIdentifier = PROP_ALL;
property_list.next = NULL;
/* configure the read access data */
read_access_data.listOfProperties = &property_list;
read_access_data.object_instance = object_instance;
read_access_data.object_type = object_type;
read_access_data.next = NULL;
return Send_Read_Property_Multiple_Request(
pdu, sizeof(pdu), device_id, &read_access_data);
}
/**
* @brief Handles the ReadProperty process
* @param service_request [in] The contents of the service request.
* @return true if the process is finished
*/
static bool bacnet_read_write_process(TARGET_DATA *target)
{
bool found = false;
unsigned max_apdu = 0;
uint8_t application_data[16] = { 0 };
int application_data_len = 0;
bool valid_tag = false;
switch (RW_State) {
case BACNET_CLIENT_IDLE:
mstimer_set(&Read_Write_Timer, apdu_timeout());
if (target->device_id < BACNET_MAX_INSTANCE) {
Error_Detected = false;
RW_State = BACNET_CLIENT_BIND;
} else {
RW_State = BACNET_CLIENT_FINISHED;
}
break;
case BACNET_CLIENT_BIND:
/* exclude our device - in case our ID changed */
address_own_device_id_set(Device_Object_Instance_Number());
/* try to bind with the device */
found = address_bind_request(
target->device_id, &max_apdu, &Target_Address);
if (found) {
Target_Device_ID = target->device_id;
RW_State = BACNET_CLIENT_SEND;
} else {
Send_WhoIs(target->device_id, target->device_id);
RW_State = BACNET_CLIENT_BINDING;
}
break;
case BACNET_CLIENT_BINDING:
found = address_bind_request(
target->device_id, &max_apdu, &Target_Address);
if (found) {
Target_Device_ID = target->device_id;
mstimer_set(&Read_Write_Timer, apdu_timeout());
RW_State = BACNET_CLIENT_SEND;
} else if (mstimer_expired(&Read_Write_Timer)) {
/* unable to bind within APDU timeout */
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = ERROR_CODE_TIMEOUT;
RW_State = BACNET_CLIENT_FINISHED;
}
break;
case BACNET_CLIENT_SEND:
if (target->write_property) {
switch (target->tag) {
case BACNET_APPLICATION_TAG_NULL:
application_data_len =
encode_application_null(&application_data[0]);
valid_tag = true;
break;
case BACNET_APPLICATION_TAG_BOOLEAN:
application_data_len = encode_application_boolean(
&application_data[0], target->type.Boolean);
valid_tag = true;
break;
case BACNET_APPLICATION_TAG_REAL:
application_data_len = encode_application_real(
&application_data[0], target->type.Real);
valid_tag = true;
break;
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
application_data_len = encode_application_unsigned(
&application_data[0], target->type.Unsigned_Int);
valid_tag = true;
break;
case BACNET_APPLICATION_TAG_SIGNED_INT:
application_data_len = encode_application_signed(
&application_data[0], target->type.Signed_Int);
valid_tag = true;
break;
case BACNET_APPLICATION_TAG_ENUMERATED:
application_data_len = encode_application_enumerated(
&application_data[0], target->type.Enumerated);
valid_tag = true;
break;
default:
break;
}
if (valid_tag) {
Request_Invoke_ID = Send_Write_Property_Request_Data(
target->device_id, target->object_type,
target->object_instance, target->object_property,
&application_data[0], application_data_len,
target->priority, target->array_index);
}
} else {
if (target->object_property == PROP_ALL) {
Request_Invoke_ID = Send_RPM_All_Request(target->device_id,
target->object_type, target->object_instance);
} else {
Request_Invoke_ID =
Send_Read_Property_Request(target->device_id,
target->object_type, target->object_instance,
target->object_property, target->array_index);
}
}
if (Request_Invoke_ID == 0) {
if (mstimer_expired(&Read_Write_Timer)) {
/* TSM Timeout - no invokeIDs available */
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = ERROR_CODE_TIMEOUT;
RW_State = BACNET_CLIENT_FINISHED;
}
} else {
RW_State = BACNET_CLIENT_WAITING;
}
break;
case BACNET_CLIENT_WAITING:
if (tsm_invoke_id_free(Request_Invoke_ID)) {
Error_Detected = false;
RW_State = BACNET_CLIENT_FINISHED;
} else if (tsm_invoke_id_failed(Request_Invoke_ID)) {
Error_Detected = true;
Error_Class = ERROR_CLASS_SERVICES;
Error_Code = ERROR_CODE_ABORT_TSM_TIMEOUT;
RW_State = BACNET_CLIENT_FINISHED;
tsm_free_invoke_id(Request_Invoke_ID);
} else if (Error_Detected) {
RW_State = BACNET_CLIENT_FINISHED;
}
break;
case BACNET_CLIENT_FINISHED:
RW_State = BACNET_CLIENT_IDLE;
break;
default:
break;
}
return (RW_State == BACNET_CLIENT_FINISHED);
}
/**
* @brief Sets the callback for when a read-property returns data
*
* @param callback - function for callback
*/
void bacnet_read_write_value_callback_set(
bacnet_read_write_value_callback_t callback)
{
bacnet_read_write_value_callback = callback;
}
/**
* @brief Handles the ReadProperty repetitive task
*/
void bacnet_read_write_task(void)
{
TARGET_DATA *target;
bool status = false;
BACNET_READ_PROPERTY_DATA rp_data;
if (!Ringbuf_Empty(&Target_Data_Queue)) {
target = (TARGET_DATA *)Ringbuf_Peek(&Target_Data_Queue);
status = bacnet_read_write_process(target);
if (status) {
if (Error_Detected) {
if (bacnet_read_write_value_callback) {
rp_data.error_class = Error_Class;
rp_data.error_code = Error_Code;
rp_data.object_type = target->object_type;
rp_data.object_instance = target->object_instance;
rp_data.object_property = target->object_property;
rp_data.array_index = target->array_index;
bacnet_read_write_value_callback(
target->device_id, &rp_data, NULL);
}
}
Ringbuf_Pop(&Target_Data_Queue, NULL);
}
}
if (mstimer_expired(&Cache_Timer)) {
mstimer_reset(&Cache_Timer);
address_cache_timer(CACHE_CYCLE_SECONDS);
}
}
/**
* @brief Adds a Read Property request remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_read_property_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target;
target.write_property = false;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a WriteProperty request to a remote data point - REAL
* @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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type REAL (float)
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_real_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
float value,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target = { 0 };
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_REAL;
target.type.Real = value;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a Write Property request to a remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type NULL
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_null_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target = { 0 };
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_NULL;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a Write Property request to a remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type ENUMERATED
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_enumerated_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
unsigned int value,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target;
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_ENUMERATED;
target.type.Enumerated = value;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a Write Property request to a remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type UNSIGNED INT
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_unsigned_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
unsigned int value,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target;
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
target.type.Unsigned_Int = value;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a Write Property request to a remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type SIGNED INT
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_signed_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
signed int value,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target;
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_SIGNED_INT;
target.type.Signed_Int = value;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Adds a Write Property request to a remote data 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.
* @param object_property - Property to be read, but not ALL, REQUIRED, or
* OPTIONAL.
* @param value - property value of type SIGNED INT
* @param priority - BACnet priority for writing 1..16, or 0 if not set
* @param array_index [in] Optional: if the Property is an array,
* - 0 for the array size
* - 1 to n for individual array members
* - BACNET_ARRAY_ALL (~0) for the full array to be read.
* @return true if added, false if not added
*/
bool bacnet_write_property_boolean_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
bool value,
uint8_t priority,
uint32_t array_index)
{
bool status = false;
TARGET_DATA target;
target.write_property = true;
target.device_id = device_id;
target.object_type = object_type;
target.object_instance = object_instance;
target.object_property = object_property;
target.tag = BACNET_APPLICATION_TAG_BOOLEAN;
target.type.Boolean = value;
target.priority = priority;
target.array_index = array_index;
status = Ringbuf_Put(&Target_Data_Queue, (uint8_t *)&target);
return status;
}
/**
* @brief Determines if the BACnet ReadProperty queue is empty
* @return true if the parameter queue is empty, and thus, idle
*/
bool bacnet_read_write_idle(void)
{
return Ringbuf_Empty(&Target_Data_Queue);
}
/**
* @brief Determines if the BACnet ReadProperty queue is full
* @return true if the parameter queue is full, and thus, busy
*/
bool bacnet_read_write_busy(void)
{
return Ringbuf_Full(&Target_Data_Queue);
}
/**
* @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_read_write_vendor_id_filter_set(uint16_t vendor_id)
{
Target_Vendor_ID = vendor_id;
}
/**
* @brief Initializes the ReadProperty module
*/
void bacnet_read_write_init(void)
{
Ringbuf_Init(&Target_Data_Queue, (uint8_t *)&Target_Data_Buffer,
TARGET_DATA_QUEUE_SIZE, TARGET_DATA_QUEUE_COUNT);
/* handle i-am to support binding to other devices */
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, My_I_Am_Bind);
/* handle the data coming back from confirmed requests */
apdu_set_confirmed_ack_handler(
SERVICE_CONFIRMED_READ_PROPERTY, My_Read_Property_Ack_Handler);
apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROP_MULTIPLE,
My_Read_Property_Multiple_Ack_Handler);
/* handle the Simple ACK coming back */
apdu_set_confirmed_simple_ack_handler(
SERVICE_CONFIRMED_WRITE_PROPERTY, MyWritePropertySimpleAckHandler);
/* handle any errors coming back */
apdu_set_error_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyErrorHandler);
apdu_set_error_handler(SERVICE_CONFIRMED_WRITE_PROPERTY, MyErrorHandler);
apdu_set_abort_handler(MyAbortHandler);
apdu_set_reject_handler(MyRejectHandler);
/* configure the address cache */
address_init();
/* start the cyclic 1 second timer for Address Cache */
mstimer_set(&Cache_Timer, CACHE_CYCLE_SECONDS * 1000);
}
+105
View File
@@ -0,0 +1,105 @@
/**
* @file
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2013
*
* SPDX-License-Identifier: MIT
*/
#ifndef BAC_RW_H
#define BAC_RW_H
#include <stdint.h>
#include "bacnet/bacdef.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacapp.h"
#include "bacnet/rp.h"
#include "bacnet/bacnet_stack_exports.h"
/**
* Save the requested ReadProperty data to a data store
*
* @param device_instance [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.
*/
typedef void (*bacnet_read_write_value_callback_t)(uint32_t device_instance,
BACNET_READ_PROPERTY_DATA *rp_data,
BACNET_APPLICATION_DATA_VALUE *value);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
void bacnet_read_write_init(void);
BACNET_STACK_EXPORT
void bacnet_read_write_task(void);
BACNET_STACK_EXPORT
bool bacnet_read_write_idle(void);
BACNET_STACK_EXPORT
bool bacnet_read_write_busy(void);
BACNET_STACK_EXPORT
bool bacnet_read_property_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_real_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
float value,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_null_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_enumerated_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
unsigned int value,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_unsigned_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
unsigned int value,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_signed_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
signed int value,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
bool bacnet_write_property_boolean_queue(uint32_t device_id,
BACNET_OBJECT_TYPE object_type,
uint32_t object_instance,
BACNET_PROPERTY_ID object_property,
bool value,
uint8_t priority,
uint32_t array_index);
BACNET_STACK_EXPORT
void bacnet_read_write_value_callback_set(
bacnet_read_write_value_callback_t callback);
BACNET_STACK_EXPORT
void bacnet_read_write_vendor_id_filter_set(uint16_t vendor_id);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+108
View File
@@ -0,0 +1,108 @@
/**
* @file
* @author Steve Karg
* @date 2013
* @brief High level BACnet Task handling
*
* SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/* core library */
#include "bacnet/apdu.h"
#include "bacnet/bacdef.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacerror.h"
#include "bacnet/config.h"
#include "bacnet/dcc.h"
#include "bacnet/iam.h"
#include "bacnet/npdu.h"
#include "bacnet/version.h"
#include "bacnet/whois.h"
/* basic services */
#include "bacnet/basic/binding/address.h"
#include "bacnet/basic/sys/filename.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/services.h"
#include "bacnet/basic/tsm/tsm.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/datalink/dlenv.h"
#include "bacnet/basic/object/device.h"
/* us */
#include "bacnet/basic/client/bac-rw.h"
#include "bacnet/basic/client/bac-data.h"
#include "bacnet/basic/client/bac-task.h"
/** Buffer used for receiving */
static uint8_t Rx_Buf[MAX_MPDU];
/* task timer for various BACnet timeouts */
static struct mstimer BACnet_Task_Timer;
/* task timer for TSM timeouts */
static struct mstimer BACnet_TSM_Timer;
/**
* @brief Non-blocking task for running BACnet service
*/
void bacnet_task(void)
{
static bool initialized = false;
BACNET_ADDRESS src = { 0 }; /* address where message came from */
uint16_t pdu_len = 0;
const unsigned timeout_ms = 5;
if (!initialized) {
initialized = true;
/* broadcast an I-Am on startup */
Send_I_Am(&Handler_Transmit_Buffer[0]);
}
/* input */
/* returns 0 bytes on timeout */
pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout_ms);
/* process */
if (pdu_len) {
npdu_handler(&src, &Rx_Buf[0], pdu_len);
}
/* 1 second tasks */
if (mstimer_expired(&BACnet_Task_Timer)) {
mstimer_reset(&BACnet_Task_Timer);
dcc_timer_seconds(1);
datalink_maintenance_timer(1);
dlenv_maintenance_timer(1);
}
if (mstimer_expired(&BACnet_TSM_Timer)) {
mstimer_reset(&BACnet_TSM_Timer);
tsm_timer_milliseconds(mstimer_interval(&BACnet_TSM_Timer));
}
bacnet_data_task();
}
/**
* @brief Initialize the handlers we will utilize.
*/
void bacnet_task_init(void)
{
Device_Init(NULL);
/* we need to handle who-is to support dynamic device binding */
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
/* we need to handle who-has to support dynamic object binding */
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
/* set the handler for all the services we don't implement */
/* It is required to send the proper reject message... */
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
/* Set the handlers for any confirmed services that we support. */
/* We must implement read property - it's required! */
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
/* handle communication so we can shutup when asked */
apdu_set_confirmed_handler(SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL,
handler_device_communication_control);
bacnet_data_init();
mstimer_set(&BACnet_Task_Timer, 1000);
mstimer_set(&BACnet_TSM_Timer, 50);
}
+27
View File
@@ -0,0 +1,27 @@
/**
* @file
* @author Steve Karg
* @date 2013
* @brief High level BACnet task handling
*
* SPDX-License-Identifier: MIT
*/
#ifndef BACNET_TASK_H
#define BACNET_TASK_H
#include <stdint.h>
#include "bacnet/bacnet_stack_exports.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
void bacnet_task_init(void);
BACNET_STACK_EXPORT
void bacnet_task(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif