/** * @file * @author Steve Karg * @date 2013 * @brief Store properties from other BACnet devices * * SPDX-License-Identifier: MIT */ #include #include #include #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 != NULL); assert(value != NULL); 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); }