883 lines
31 KiB
C
883 lines
31 KiB
C
/**
|
|
* @file
|
|
* @brief Read properties from other BACnet devices, and store their values
|
|
* @author Steve Karg <skarg@users.sourceforge.net>
|
|
* @date 2022
|
|
* @copyright 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;
|
|
/* where the data from the I-Am is called */
|
|
static bacnet_read_write_device_callback_t bacnet_read_write_device_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);
|
|
if (bacnet_read_write_device_callback) {
|
|
bacnet_read_write_device_callback(
|
|
device_id, max_apdu, segmentation, vendor_id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 */
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Process a ReadProperty-ACK message
|
|
* @param device_id [in] The device ID of the source of the message
|
|
* @param rp_data [in] The contents of the service request.
|
|
*/
|
|
static void bacnet_read_property_ack_process(
|
|
uint32_t device_id, BACNET_READ_PROPERTY_DATA *rp_data)
|
|
{
|
|
BACNET_APPLICATION_DATA_VALUE *value;
|
|
uint8_t *apdu;
|
|
int apdu_len, len;
|
|
BACNET_ARRAY_INDEX array_index = 0;
|
|
|
|
if (rp_data) {
|
|
apdu = rp_data->application_data;
|
|
apdu_len = rp_data->application_data_len;
|
|
while (apdu_len) {
|
|
value = &Target_Decoded_Property_Value;
|
|
len = bacapp_decode_known_property(apdu, (unsigned)apdu_len, value,
|
|
rp_data->object_type, rp_data->object_property);
|
|
if (len > 0) {
|
|
if ((len < apdu_len) &&
|
|
(rp_data->array_index == BACNET_ARRAY_ALL)) {
|
|
/* assume that since there is more data that this
|
|
is an array and split full array of elements
|
|
into separate RP Acks */
|
|
array_index = 1;
|
|
}
|
|
rp_data->error_class = ERROR_CLASS_SERVICES;
|
|
rp_data->error_code = ERROR_CODE_SUCCESS;
|
|
if (array_index) {
|
|
rp_data->array_index = array_index;
|
|
}
|
|
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 < apdu_len) {
|
|
apdu += len;
|
|
apdu_len -= len;
|
|
if (array_index) {
|
|
array_index++;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
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, NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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;
|
|
uint32_t device_id = 0;
|
|
|
|
if (address_match(&Target_Address, src) &&
|
|
(service_data->invoke_id == Request_Invoke_ID)) {
|
|
address_get_device_id(src, &device_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 {
|
|
bacnet_read_property_ack_process(device_id, &rp_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** 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 *apdu,
|
|
uint16_t apdu_len,
|
|
BACNET_ADDRESS *src,
|
|
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
|
|
{
|
|
BACNET_READ_PROPERTY_DATA rp_data = { 0 };
|
|
uint32_t device_id = 0;
|
|
|
|
address_get_device_id(src, &device_id);
|
|
if (address_match(&Target_Address, src) &&
|
|
(service_data->invoke_id == Request_Invoke_ID)) {
|
|
rpm_ack_object_property_process(apdu, apdu_len,
|
|
device_id, &rp_data,
|
|
bacnet_read_property_ack_process);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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 Sets the callback for when an I-Am returns device data
|
|
*
|
|
* @param callback - function for callback
|
|
*/
|
|
void bacnet_read_write_device_callback_set(
|
|
bacnet_read_write_device_callback_t callback)
|
|
{
|
|
bacnet_read_write_device_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 Gets a Vendor ID filter on I-Am bindings to limit the address
|
|
* cache usage when we are only reading/writing to a specific vendor ID
|
|
* @return vendor_id - vendor ID to filter, 0=no filter
|
|
*/
|
|
uint16_t bacnet_read_write_vendor_id_filter(void)
|
|
{
|
|
return Target_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);
|
|
}
|