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:
+13
-10
@@ -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
@@ -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 $@
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user