From 8c66eac7634e443e42b42fb5a6e5386ec8f2010b Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 11 Apr 2025 00:26:32 -0700 Subject: [PATCH 1/5] Add characterctring-value object support --- CMakeLists.txt | 1 + lib/bacnet/gateway/object.ex | 11 ++ src/bacnet.c | 53 ++++++ src/object/characterstring_value.c | 292 +++++++++++++++++++++++++++++ src/object/characterstring_value.h | 50 +++++ src/protocol/decode_call.c | 32 ++++ src/protocol/decode_call.h | 9 + 7 files changed, 448 insertions(+) create mode 100644 src/object/characterstring_value.c create mode 100644 src/object/characterstring_value.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d403c9f..e29705b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set(SOURCES src/log.c src/main.c src/port.c + src/object/characterstring_value.c src/object/command.c src/protocol/decode_call.c src/protocol/enum.c diff --git a/lib/bacnet/gateway/object.ex b/lib/bacnet/gateway/object.ex index 5a5ad55..253db5d 100644 --- a/lib/bacnet/gateway/object.ex +++ b/lib/bacnet/gateway/object.ex @@ -163,4 +163,15 @@ defmodule BACNet.Gateway.Object do status }) end + + def create_characterstring_value(pid, device_id, object_id, name, description, value) do + GenServer.call(pid, { + :create_characterstring_value, + device_id, + object_id, + name, + description, + value + }) + end end diff --git a/src/bacnet.c b/src/bacnet.c index 6915c9c..f31aa9c 100644 --- a/src/bacnet.c +++ b/src/bacnet.c @@ -12,6 +12,7 @@ #include "bacnet.h" #include "log.h" #include "protocol/decode_call.h" +#include "object/characterstring_value.h" #include "object/command.h" #define REPLY_OK(reply) \ @@ -49,6 +50,10 @@ handle_set_routed_multistate_value(set_routed_multistate_input_value_t* params); static int handle_create_routed_command(create_routed_command_t* params); static int handle_set_routed_command_status(set_routed_command_status_t* params); +static int +handle_create_characterstring_value(create_characterstring_value_t* params); +handle_create_characterstring_value(create_characterstring_value_t *params); + static call_handler_t CALL_HANDLERS_BY_TYPE[] = { (call_handler_t)handle_create_gateway, (call_handler_t)handle_create_routed_device, @@ -58,6 +63,7 @@ static call_handler_t CALL_HANDLERS_BY_TYPE[] = { (call_handler_t)handle_set_routed_multistate_value, (call_handler_t)handle_create_routed_command, (call_handler_t)handle_set_routed_command_status, + (call_handler_t)handle_create_characterstring_value, }; /** @@ -300,6 +306,28 @@ static object_functions_t SUPPORTED_OBJECT_TABLE[] = { .Object_Delete = NULL, .Object_Timer = NULL, }, + { + .Object_Type = OBJECT_CHARACTERSTRING_VALUE, + .Object_Init = characterstring_value_init, + .Object_Count = characterstring_value_count, + .Object_Index_To_Instance = characterstring_value_index_to_instance, + .Object_Valid_Instance = characterstring_value_valid_instance, + .Object_Name = characterstring_value_name, + .Object_Read_Property = characterstring_value_read_property, + .Object_Write_Property = NULL, + .Object_RPM_List = command_property_lists, + .Object_RR_Info = NULL, + .Object_Iterator = NULL, + .Object_Value_List = NULL, + .Object_COV = NULL, + .Object_COV_Clear = NULL, + .Object_Intrinsic_Reporting = NULL, + .Object_Add_List_Element = NULL, + .Object_Remove_List_Element = NULL, + .Object_Create = NULL, + .Object_Delete = NULL, + .Object_Timer = NULL, + }, }; static int init_service_handlers() @@ -560,3 +588,28 @@ static int handle_set_routed_command_status(set_routed_command_status_t* params) return 0; } + +static int +handle_create_characterstring_value(create_characterstring_value_t* params) +{ + uint32_t device_index = + Routed_Device_Instance_To_Index(params->device_bacnet_id); + + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(device_index); + + uint32_t bacnet_id = + characterstring_value_create( + device, + params->object_bacnet_id, + params->name, + params->description, + params->value + ); + + if (bacnet_id != params->object_bacnet_id) + return -1; + + Get_Routed_Device_Object(0); + + return 0; +} diff --git a/src/object/characterstring_value.c b/src/object/characterstring_value.c new file mode 100644 index 0000000..b2a18f4 --- /dev/null +++ b/src/object/characterstring_value.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "object/characterstring_value.h" +#include "protocol/event.h" + +static const int required_properties[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, + PROP_STATUS_FLAGS, + -1 +}; + +static const int optional_properties[] = { + PROP_EVENT_STATE, + PROP_OUT_OF_SERVICE, + PROP_DESCRIPTION, + -1 +}; + +static const int proprietary_properties[] = { -1 }; + +/** + * @brief Handles any setup required to create character-string objects. + */ +void characterstring_value_init(void) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + if (device->objects == NULL) + device->objects = Keylist_Create(); +} + +/** + * @brief Create a character-string object. + * + * @param instance - Object instance number. + * + * @return BACNET_MAX_INSTANCE on error and a valid instance number on success. + */ +uint32_t characterstring_value_create( + DEVICE_OBJECT_DATA* device, + uint32_t instance, + char* name, + char* description, + char* value +) { + if (instance >= BACNET_MAX_INSTANCE) + return BACNET_MAX_INSTANCE; + + if (strlen(name) <= 0) + return BACNET_MAX_INSTANCE; + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, instance); + + if (object != NULL) + return instance; + + object = calloc(1, sizeof(CHARACTERSTRING_VALUE_OBJECT)); + if (!object) + return BACNET_MAX_INSTANCE; + + object->type = OBJECT_CHARACTERSTRING_VALUE; + + memset(object->name, 0, sizeof(object->name)); + memset(object->description, 0, sizeof(object->description)); + memset(object->present_value, 0, sizeof(object->present_value)); + + memcpy(object->name, name, strlen(name)); + memcpy(object->description, description, strlen(description)); + memcpy(object->present_value, value, strlen(value)); + + if (Keylist_Data_Add(device->objects, instance, object) < 0) { + free(object); + return BACNET_MAX_INSTANCE; + } + + return instance; +} + +/** + * @brief Returns the count of character-string value objects for the currently + * selected Device. + */ +unsigned characterstring_value_count(void) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + unsigned count = + Routed_Object_Count_By_Type( + device->objects, + OBJECT_CHARACTERSTRING_VALUE + ); + + return count; +} + +/** + * @brief Get the character-string's instace number from its index. + * + * @param index - Index of the object from within the Devices Object's list. + * + * @return BACnet Object instance number. + */ +uint32_t characterstring_value_index_to_instance(unsigned index) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + KEY key = UINT32_MAX; + Routed_Object_Index_Key( + device->objects, + OBJECT_CHARACTERSTRING_VALUE, + index, + &key + ); + + return key; +} + +bool characterstring_value_valid_instance(uint32_t instance) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, instance); + + if (!object) return false; + if (object->type != OBJECT_CHARACTERSTRING_VALUE) return false; + + return true; +} + +bool +characterstring_value_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, instance); + + if (object == NULL) return false; + if (strlen(object->name) <= 0) return false; + + return characterstring_init_ansi(name, object->name); +} + +bool characterstring_value_description( + uint32_t instance, + BACNET_CHARACTER_STRING* description +) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, instance); + + if (object == NULL) return false; + if (strlen(object->description) <= 0) return false; + + return characterstring_init_ansi(description, object->description); +} + +bool characterstring_value_present_value( + uint32_t instance, + BACNET_CHARACTER_STRING* value +) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, instance); + + if (object == NULL) return false; + if (strlen(object->present_value) <= 0) return false; + + return characterstring_init_ansi(value, object->present_value); +} + +int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data) +{ + bool is_data_invalid = + data == NULL + || data->application_data == NULL + || data->application_data_len == 0; + + if (is_data_invalid) return 0; + + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + CHARACTERSTRING_VALUE_OBJECT* object = + Keylist_Data(device->objects, data->object_instance); + + if (!object) { + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + return BACNET_STATUS_ERROR; + } + + int apdu_len = 0; + uint8_t* apdu = data->application_data; + + switch (data->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id( + &apdu[0], + OBJECT_CHARACTERSTRING_VALUE, + data->object_instance + ); + break; + + case PROP_OBJECT_NAME: + BACNET_CHARACTER_STRING name; + characterstring_value_name(data->object_instance, &name); + apdu_len = encode_application_character_string(&apdu[0], &name); + break; + + case PROP_DESCRIPTION: + BACNET_CHARACTER_STRING description; + characterstring_value_description(data->object_instance, &description); + apdu_len = encode_application_character_string(&apdu[0], &description); + break; + + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], OBJECT_CHARACTERSTRING_VALUE); + break; + + case PROP_PRESENT_VALUE: + BACNET_CHARACTER_STRING present_value; + characterstring_value_present_value(data->object_instance, &present_value); + apdu_len = encode_application_character_string(&apdu[0], &present_value); + break; + + case PROP_STATUS_FLAGS: + BACNET_BIT_STRING status; + bitstring_init(&status); + + bitstring_set_bit(&status, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&status, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&status, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&status, STATUS_FLAG_OUT_OF_SERVICE, false); + + apdu_len = encode_application_bitstring(&apdu[0], &status); + break; + + case PROP_EVENT_STATE: + apdu_len = encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + + case PROP_OUT_OF_SERVICE: + apdu_len = encode_application_boolean(&apdu[0], false); + break; + + default: + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + + if (apdu_len < 0) return apdu_len; + + bool requesting_array_index = data->array_index != BACNET_ARRAY_ALL; + if (requesting_array_index && data->object_property != PROP_STATE_TEXT) { + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Attempts to set required, optional and proprietary properties. + * + * @note All params are sentinel terminated list of integers. + * @param required - BACnet required properties for a character-string object. + * @param optional - BACnet optional properties for a character-string object. + * @param proprietary - BACnet proprietary properties for a character-string + * object. + */ +void characterstring_value_property_lists( + const int** required, + const int** optional, + const int** proprietary +) { + if (required) *required = required_properties; + if (optional) *optional = optional_properties; + if (proprietary) *proprietary = proprietary_properties; +} diff --git a/src/object/characterstring_value.h b/src/object/characterstring_value.h new file mode 100644 index 0000000..4febf8a --- /dev/null +++ b/src/object/characterstring_value.h @@ -0,0 +1,50 @@ +#ifndef BACNET_OBJECT_CHARACTERSTRING_VALUE_H +#define BACNET_OBJECT_CHARACTERSTRING_VALUE_H + +#include + +#define MAX_OBJ_NAME_LEN 128 +#define MAX_OBJ_DESC_LEN 128 +#define MAX_OBJ_VALUE_LEN 128 + +typedef struct { + BACNET_OBJECT_TYPE type; + + char name[MAX_OBJ_NAME_LEN]; + char description[MAX_OBJ_DESC_LEN]; + char present_value[MAX_OBJ_VALUE_LEN]; +} CHARACTERSTRING_VALUE_OBJECT; + +void characterstring_value_init(void); + +uint32_t characterstring_value_create( + DEVICE_OBJECT_DATA* device, + uint32_t instance, + char* name, + char* description, + char* value); + +unsigned characterstring_value_count(void); +uint32_t characterstring_value_index_to_instance(unsigned index); +bool characterstring_value_valid_instance(uint32_t instance); + +bool characterstring_value_name( + uint32_t instance, + BACNET_CHARACTER_STRING* name); + +bool characterstring_value_description( + uint32_t instance, + BACNET_CHARACTER_STRING* description); + +bool characterstring_value_present_value( + uint32_t instance, + BACNET_CHARACTER_STRING* value); + +int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data); + +void characterstring_value_property_lists( + const int** required, + const int** optional, + const int** proprietary); + +#endif /* BACNET_OBJECT_CHARACTERSTRING_VALUE_H */ diff --git a/src/protocol/decode_call.c b/src/protocol/decode_call.c index 798d5a9..745ba5d 100644 --- a/src/protocol/decode_call.c +++ b/src/protocol/decode_call.c @@ -13,6 +13,7 @@ const enum_tuple_t BACNET_CALL_ATOMS[] = { {"set_routed_multistate_input_value", CALL_SET_ROUTED_MULTISTATE_INPUT_VALUE}, {"create_routed_command", CALL_CREATE_ROUTED_COMMAND}, {"set_routed_command_status", CALL_SET_ROUTED_COMMAND_STATUS}, + {"create_characterstring_value", CALL_CREATE_CHARACTERSTRING_VALUE}, }; const size_t BACNET_CALL_SIZE_LOOKUP[] = { @@ -24,6 +25,7 @@ const size_t BACNET_CALL_SIZE_LOOKUP[] = { sizeof(set_routed_multistate_input_value_t), sizeof(create_routed_command_t), sizeof(set_routed_command_status_t), + sizeof(create_characterstring_value_t), }; static int decode_call_type(char* buffer, int* index, uint8_t* type); @@ -354,6 +356,33 @@ static int decode_set_routed_command_status( return is_invalid ? -1 : 0; } +static int decode_create_characterstring_value( + char* buffer, + int* index, + create_characterstring_value_t* data +) { + long size = 0; + int type = 0; + + bool is_invalid = + ei_decode_ulong(buffer, index, (unsigned long*)&data->device_bacnet_id) + || ei_decode_ulong(buffer, index, (unsigned long*)&data->object_bacnet_id) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->name)) + || (memset(data->name, 0, sizeof(data->name)) == NULL) + || ei_decode_binary(buffer, index, data->name, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->description)) + || (memset(data->description, 0, sizeof(data->description)) == NULL) + || ei_decode_binary(buffer, index, data->description, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->value)) + || (memset(data->value, 0, sizeof(data->value)) == NULL) + || ei_decode_binary(buffer, index, data->value, &size); + + return is_invalid ? -1 : 0; +} + static int decode_call_data( char* buffer, int* index, @@ -385,6 +414,9 @@ static int decode_call_data( case CALL_SET_ROUTED_COMMAND_STATUS: return decode_set_routed_command_status(buffer, index, data); + case CALL_CREATE_CHARACTERSTRING_VALUE: + return decode_create_characterstring_value(buffer, index, data); + default: return -1; } diff --git a/src/protocol/decode_call.h b/src/protocol/decode_call.h index a0cab9c..b9f92b3 100644 --- a/src/protocol/decode_call.h +++ b/src/protocol/decode_call.h @@ -12,6 +12,7 @@ typedef enum { CALL_SET_ROUTED_MULTISTATE_INPUT_VALUE, CALL_CREATE_ROUTED_COMMAND, CALL_SET_ROUTED_COMMAND_STATUS, + CALL_CREATE_CHARACTERSTRING_VALUE, CALL_UNKNOWN = 255, } __attribute__((packed)) bacnet_call_type_t; @@ -71,6 +72,14 @@ typedef struct { bacnet_command_status_t status; } set_routed_command_status_t; +typedef struct { + uint32_t device_bacnet_id; + uint32_t object_bacnet_id; + char name[MAXATOMLEN]; + char description[MAXATOMLEN]; + char value[MAXATOMLEN]; +} create_characterstring_value_t; + int bacnet_call_malloc(bacnet_call_type_t type, void** call); int decode_bacnet_call_type(char* buffer, int* index, bacnet_call_type_t* type); From 44eb437c9a8cbc0442b692f5de7ccef884f40dbd Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 11 Apr 2025 00:29:43 -0700 Subject: [PATCH 2/5] Add binary-input object support --- CMakeLists.txt | 1 + lib/bacnet/gateway/object.ex | 23 +++ src/bacnet.c | 73 +++++++- src/object/binary_input.c | 316 +++++++++++++++++++++++++++++++++++ src/object/binary_input.h | 44 +++++ src/protocol/decode_call.c | 78 +++++++++ src/protocol/decode_call.h | 20 +++ 7 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 src/object/binary_input.c create mode 100644 src/object/binary_input.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e29705b..da58d84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ set(SOURCES src/log.c src/main.c src/port.c + src/object/binary_input.c src/object/characterstring_value.c src/object/command.c src/protocol/decode_call.c diff --git a/lib/bacnet/gateway/object.ex b/lib/bacnet/gateway/object.ex index 253db5d..8b92aed 100644 --- a/lib/bacnet/gateway/object.ex +++ b/lib/bacnet/gateway/object.ex @@ -174,4 +174,27 @@ defmodule BACNet.Gateway.Object do value }) end + + def create_binary_input(pid, device_id, object_id, name, description, active_text, inactive_text, polarity, value) do + GenServer.call(pid, { + :create_binary_input, + device_id, + object_id, + name, + description, + active_text, + inactive_text, + polarity, + value + }) + end + + def set_binary_input_present_value(pid, device_id, object_id, value) do + GenServer.call(pid, { + :set_binary_input_value, + device_id, + object_id, + value + }) + end end diff --git a/src/bacnet.c b/src/bacnet.c index f31aa9c..a0f175e 100644 --- a/src/bacnet.c +++ b/src/bacnet.c @@ -12,6 +12,7 @@ #include "bacnet.h" #include "log.h" #include "protocol/decode_call.h" +#include "object/binary_input.h" #include "object/characterstring_value.h" #include "object/command.h" @@ -52,7 +53,9 @@ static int handle_set_routed_command_status(set_routed_command_status_t* params) static int handle_create_characterstring_value(create_characterstring_value_t* params); -handle_create_characterstring_value(create_characterstring_value_t *params); + +static int handle_create_binary_input(create_binary_input_t* params); +static int handle_set_binary_input_value(set_binary_input_value_t* params); static call_handler_t CALL_HANDLERS_BY_TYPE[] = { (call_handler_t)handle_create_gateway, @@ -64,6 +67,8 @@ static call_handler_t CALL_HANDLERS_BY_TYPE[] = { (call_handler_t)handle_create_routed_command, (call_handler_t)handle_set_routed_command_status, (call_handler_t)handle_create_characterstring_value, + (call_handler_t)handle_create_binary_input, + (call_handler_t)handle_set_binary_input_value, }; /** @@ -328,6 +333,28 @@ static object_functions_t SUPPORTED_OBJECT_TABLE[] = { .Object_Delete = NULL, .Object_Timer = NULL, }, + { + .Object_Type = OBJECT_BINARY_INPUT, + .Object_Init = binary_input_init, + .Object_Count = binary_input_count, + .Object_Index_To_Instance = binary_input_index_to_instance, + .Object_Valid_Instance = binary_input_valid_instance, + .Object_Name = binary_input_name, + .Object_Read_Property = binary_input_read_property, + .Object_Write_Property = NULL, + .Object_RPM_List = binary_input_property_lists, + .Object_RR_Info = NULL, + .Object_Iterator = NULL, + .Object_Value_List = NULL, + .Object_COV = NULL, + .Object_COV_Clear = NULL, + .Object_Intrinsic_Reporting = NULL, + .Object_Add_List_Element = NULL, + .Object_Remove_List_Element = NULL, + .Object_Create = NULL, + .Object_Delete = NULL, + .Object_Timer = NULL, + }, }; static int init_service_handlers() @@ -613,3 +640,47 @@ handle_create_characterstring_value(create_characterstring_value_t* params) return 0; } + +static int handle_create_binary_input(create_binary_input_t* params) +{ + uint32_t device_index = + Routed_Device_Instance_To_Index(params->device_bacnet_id); + + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(device_index); + + uint32_t bacnet_id = + binary_input_create( + device, + params->object_bacnet_id, + params->name, + params->description, + params->value, + params->polarity, + params->active_text, + params->inactive_text + ); + + if (bacnet_id != params->object_bacnet_id) + return -1; + + Get_Routed_Device_Object(0); + + return 0; +} + +static int handle_set_binary_input_value(set_binary_input_value_t* params) +{ + uint32_t device_index = + Routed_Device_Instance_To_Index(params->device_bacnet_id); + + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(device_index); + BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, params->object_bacnet_id); + + if (!object) return -1; + + binary_input_set_present_value(object, params->value); + + Get_Routed_Device_Object(0); + + return 0; +} diff --git a/src/object/binary_input.c b/src/object/binary_input.c new file mode 100644 index 0000000..9d5f079 --- /dev/null +++ b/src/object/binary_input.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "object/binary_input.h" +#include "protocol/event.h" + +static const int required_properties[] = { + PROP_OBJECT_IDENTIFIER, + PROP_OBJECT_NAME, + PROP_OBJECT_TYPE, + PROP_PRESENT_VALUE, + PROP_STATUS_FLAGS, + PROP_EVENT_STATE, + PROP_OUT_OF_SERVICE, + PROP_POLARITY, + -1 +}; + +static const int optional_properties[] = { + PROP_RELIABILITY, + PROP_DESCRIPTION, + PROP_ACTIVE_TEXT, + PROP_INACTIVE_TEXT, + -1 +}; + +static const int proprietary_properties[] = { -1 }; + +/** + * @brief Handles any setup required to create binary-input objects. + */ +void binary_input_init(void) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + if (device->objects == NULL) + device->objects = Keylist_Create(); +} + +/** + * @brief Create a character-string object. + * + * @param instance - Object instance number. + * + * @return BACNET_MAX_INSTANCE on error and a valid instance number on success. + */ +uint32_t binary_input_create( + DEVICE_OBJECT_DATA* device, + uint32_t instance, + char* name, + char* description, + bool value, + BACNET_POLARITY polarity, + char* active_text, + char* inactive_text +) { + if (instance >= BACNET_MAX_INSTANCE) + return BACNET_MAX_INSTANCE; + + if (strlen(name) <= 0) + return BACNET_MAX_INSTANCE; + + BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); + + if (object != NULL) + return instance; + + object = calloc(1, sizeof(BINARY_INPUT_OBJECT)); + if (!object) + return BACNET_MAX_INSTANCE; + + object->type = OBJECT_BINARY_INPUT; + object->polarity = polarity; + object->present_value = value; + + memset(object->name, 0, sizeof(object->name)); + memset(object->description, 0, sizeof(object->description)); + memset(object->active_text, 0, sizeof(object->active_text)); + memset(object->inactive_text, 0, sizeof(object->inactive_text)); + + memcpy(object->name, name, strlen(name)); + memcpy(object->description, description, strlen(description)); + memcpy(object->active_text, active_text, strlen(active_text)); + memcpy(object->inactive_text, inactive_text, strlen(inactive_text)); + + if (Keylist_Data_Add(device->objects, instance, object) < 0) { + free(object); + return BACNET_MAX_INSTANCE; + } + + return instance; +} + +/** + * @brief Returns the count of binary-input value objects for the currently + * selected Device. + */ +unsigned binary_input_count(void) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + unsigned count = + Routed_Object_Count_By_Type(device->objects, OBJECT_BINARY_INPUT); + + return count; +} + +/** + * @brief Get the binary-input's instace number from its index. + * + * @param index - Index of the object from within the Devices Object's list. + * + * @return BACnet Object instance number. + */ +uint32_t binary_input_index_to_instance(unsigned index) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + + KEY key = UINT32_MAX; + Routed_Object_Index_Key(device->objects, OBJECT_BINARY_INPUT, index, &key); + + return key; +} + +bool binary_input_valid_instance(uint32_t instance) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); + + if (!object) return false; + if (object->type != OBJECT_BINARY_INPUT) return false; + + return true; +} + +bool binary_input_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); + + if (object == NULL) return false; + if (strlen(object->name) <= 0) return false; + + return characterstring_init_ansi(name, object->name); +} + +bool binary_input_description( + BINARY_INPUT_OBJECT* object, + BACNET_CHARACTER_STRING* description +) { + if (strlen(object->description) <= 0) return false; + + return characterstring_init_ansi(description, object->description); +} + +bool binary_input_active_text( + BINARY_INPUT_OBJECT* object, + BACNET_CHARACTER_STRING* active_text +) { + if (strlen(object->active_text) <= 0) return false; + + return characterstring_init_ansi(active_text, object->active_text); +} + +bool binary_input_inactive_text( + BINARY_INPUT_OBJECT* object, + BACNET_CHARACTER_STRING* inactive_text +) { + if (strlen(object->inactive_text) <= 0) return false; + + return characterstring_init_ansi(inactive_text, object->inactive_text); +} + +int binary_input_read_property(BACNET_READ_PROPERTY_DATA* data) +{ + bool is_data_invalid = + data == NULL + || data->application_data == NULL + || data->application_data_len == 0; + + if (is_data_invalid) return 0; + + uint32_t instance = data->object_instance; + + DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); + BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); + + if (!object) { + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + return BACNET_STATUS_ERROR; + } + + int apdu_len = 0; + uint8_t* apdu = data->application_data; + + switch (data->object_property) { + case PROP_OBJECT_IDENTIFIER: + apdu_len = + encode_application_object_id( + &apdu[0], + OBJECT_BINARY_INPUT, + data->object_instance + ); + break; + + case PROP_OBJECT_NAME: + BACNET_CHARACTER_STRING name; + binary_input_name(data->object_instance, &name); + apdu_len = encode_application_character_string(&apdu[0], &name); + break; + + case PROP_DESCRIPTION: + BACNET_CHARACTER_STRING description; + binary_input_description(object, &description); + apdu_len = encode_application_character_string(&apdu[0], &description); + break; + + case PROP_OBJECT_TYPE: + apdu_len = + encode_application_enumerated(&apdu[0], OBJECT_BINARY_INPUT); + break; + + case PROP_PRESENT_VALUE: + apdu_len = encode_application_enumerated(&apdu[0], object->present_value); + break; + + case PROP_POLARITY: + apdu_len = encode_application_enumerated(&apdu[0], object->polarity); + break; + + case PROP_ACTIVE_TEXT: + BACNET_CHARACTER_STRING active_text; + binary_input_active_text(object, &active_text); + apdu_len = encode_application_character_string(&apdu[0], &active_text); + break; + + case PROP_INACTIVE_TEXT: + BACNET_CHARACTER_STRING inactive_text; + binary_input_inactive_text(object, &inactive_text); + apdu_len = encode_application_character_string(&apdu[0], &inactive_text); + break; + + case PROP_STATUS_FLAGS: + BACNET_BIT_STRING status; + bitstring_init(&status); + + bitstring_set_bit(&status, STATUS_FLAG_IN_ALARM, false); + bitstring_set_bit(&status, STATUS_FLAG_FAULT, false); + bitstring_set_bit(&status, STATUS_FLAG_OVERRIDDEN, false); + bitstring_set_bit(&status, STATUS_FLAG_OUT_OF_SERVICE, false); + + apdu_len = encode_application_bitstring(&apdu[0], &status); + break; + + case PROP_EVENT_STATE: + apdu_len = encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); + break; + + case PROP_OUT_OF_SERVICE: + apdu_len = encode_application_boolean(&apdu[0], false); + break; + + case PROP_RELIABILITY: + apdu_len = + encode_application_enumerated(&apdu[0], RELIABILITY_NO_FAULT_DETECTED); + break; + + default: + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + apdu_len = BACNET_STATUS_ERROR; + break; + } + + if (apdu_len < 0) return apdu_len; + + if (data->array_index != BACNET_ARRAY_ALL) { + data->error_class = ERROR_CLASS_PROPERTY; + data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Attempts to set required, optional and proprietary properties. + * + * @note All params are sentinel terminated list of integers. + * @param required - BACnet required properties for a binary-input object. + * @param optional - BACnet optional properties for a binary-input object. + * @param proprietary - BACnet proprietary properties for a binary-input object. + */ +void binary_input_property_lists( + const int** required, + const int** optional, + const int** proprietary +) { + if (required) *required = required_properties; + if (optional) *optional = optional_properties; + if (proprietary) *proprietary = proprietary_properties; +} + +/** + * @brief Update the present value. + * + * @param object - The Object to update. + * @param value - The new binary value to set. + */ +bool binary_input_set_present_value(BINARY_INPUT_OBJECT* object, bool value) +{ + object->present_value = value; + + return true; +} diff --git a/src/object/binary_input.h b/src/object/binary_input.h new file mode 100644 index 0000000..4b1a730 --- /dev/null +++ b/src/object/binary_input.h @@ -0,0 +1,44 @@ +#ifndef BACNET_OBJECT_BINARY_INPUT_H +#define BACNET_OBJECT_BINARY_INPUT_H + +#include + +#define MAX_OBJ_NAME_LEN 128 +#define MAX_OBJ_DESC_LEN 128 + +typedef struct { + BACNET_OBJECT_TYPE type; + + char name[MAX_OBJ_NAME_LEN]; + char description[MAX_OBJ_DESC_LEN]; + char active_text[MAX_OBJ_NAME_LEN]; + char inactive_text[MAX_OBJ_NAME_LEN]; + bool present_value; + + BACNET_POLARITY polarity; +} BINARY_INPUT_OBJECT; + +void binary_input_init(void); +unsigned binary_input_count(void); +uint32_t binary_input_index_to_instance(unsigned index); +bool binary_input_valid_instance(uint32_t instance); +bool binary_input_name(uint32_t instance, BACNET_CHARACTER_STRING* name); +int binary_input_read_property(BACNET_READ_PROPERTY_DATA* data); +bool binary_input_set_present_value(BINARY_INPUT_OBJECT* object, bool value); + +uint32_t binary_input_create( + DEVICE_OBJECT_DATA* device, + uint32_t instance, + char* name, + char* description, + bool value, + BACNET_POLARITY polarity, + char* active_text, + char* inactive_text); + +void binary_input_property_lists( + const int** required, + const int** optional, + const int** proprietary); + +#endif /* BACNET_OBJECT_BINARY_INPUT_H */ diff --git a/src/protocol/decode_call.c b/src/protocol/decode_call.c index 745ba5d..21c834d 100644 --- a/src/protocol/decode_call.c +++ b/src/protocol/decode_call.c @@ -14,6 +14,8 @@ const enum_tuple_t BACNET_CALL_ATOMS[] = { {"create_routed_command", CALL_CREATE_ROUTED_COMMAND}, {"set_routed_command_status", CALL_SET_ROUTED_COMMAND_STATUS}, {"create_characterstring_value", CALL_CREATE_CHARACTERSTRING_VALUE}, + {"create_binary_input", CALL_CREATE_BINARY_INPUT}, + {"set_binary_input_value", CALL_SET_BINARY_INPUT_VALUE}, }; const size_t BACNET_CALL_SIZE_LOOKUP[] = { @@ -26,6 +28,8 @@ const size_t BACNET_CALL_SIZE_LOOKUP[] = { sizeof(create_routed_command_t), sizeof(set_routed_command_status_t), sizeof(create_characterstring_value_t), + sizeof(create_binary_input_t), + sizeof(set_binary_input_value_t), }; static int decode_call_type(char* buffer, int* index, uint8_t* type); @@ -383,6 +387,74 @@ static int decode_create_characterstring_value( return is_invalid ? -1 : 0; } +const enum_tuple_t BACNET_POLARITY_ENUM_TUPLE[] = { + {"normal", POLARITY_NORMAL}, + {"reverse", POLARITY_REVERSE}, + {"max", MAX_POLARITY}, +}; + +static int decode_polarity(char* buffer, int* index, BACNET_POLARITY* polarity) +{ + char atom[MAXATOMLEN] = { 0 }; + + if (ei_decode_atom(buffer, index, atom) == -1) + return -1; + + int enum_value = find_enum_value(BACNET_POLARITY_ENUM_TUPLE, atom); + if (enum_value == -1) + return -1; + + *polarity = (BACNET_POLARITY)enum_value; + + return 0; +} + +static int decode_create_binary_input( + char* buffer, + int* index, + create_binary_input_t* data +) { + long size = 0; + int type = 0; + + bool is_invalid = + ei_decode_ulong(buffer, index, (unsigned long*)&data->device_bacnet_id) + || ei_decode_ulong(buffer, index, (unsigned long*)&data->object_bacnet_id) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->name)) + || (memset(data->name, 0, sizeof(data->name)) == NULL) + || ei_decode_binary(buffer, index, data->name, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->description)) + || (memset(data->description, 0, sizeof(data->description)) == NULL) + || ei_decode_binary(buffer, index, data->description, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->active_text)) + || (memset(data->active_text, 0, sizeof(data->active_text)) == NULL) + || ei_decode_binary(buffer, index, data->active_text, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->inactive_text)) + || (memset(data->inactive_text, 0, sizeof(data->inactive_text)) == NULL) + || ei_decode_binary(buffer, index, data->inactive_text, &size) + || decode_polarity(buffer, index, &data->polarity) + || ei_decode_boolean(buffer, index, (int*)&data->value); + + return is_invalid ? -1 : 0; +} + +static int decode_set_binary_input_value( + char* buffer, + int* index, + set_binary_input_value_t* data +) { + bool is_invalid = + ei_decode_ulong(buffer, index, (unsigned long*)&data->device_bacnet_id) + || ei_decode_ulong(buffer, index, (unsigned long*)&data->object_bacnet_id) + || ei_decode_boolean(buffer, index, (int*)&data->value); + + return is_invalid ? -1 : 0; +} + static int decode_call_data( char* buffer, int* index, @@ -417,6 +489,12 @@ static int decode_call_data( case CALL_CREATE_CHARACTERSTRING_VALUE: return decode_create_characterstring_value(buffer, index, data); + case CALL_CREATE_BINARY_INPUT: + return decode_create_binary_input(buffer, index, data); + + case CALL_SET_BINARY_INPUT_VALUE: + return decode_set_binary_input_value(buffer, index, data); + default: return -1; } diff --git a/src/protocol/decode_call.h b/src/protocol/decode_call.h index b9f92b3..9c997e8 100644 --- a/src/protocol/decode_call.h +++ b/src/protocol/decode_call.h @@ -13,6 +13,8 @@ typedef enum { CALL_CREATE_ROUTED_COMMAND, CALL_SET_ROUTED_COMMAND_STATUS, CALL_CREATE_CHARACTERSTRING_VALUE, + CALL_CREATE_BINARY_INPUT, + CALL_SET_BINARY_INPUT_VALUE, CALL_UNKNOWN = 255, } __attribute__((packed)) bacnet_call_type_t; @@ -80,6 +82,24 @@ typedef struct { char value[MAXATOMLEN]; } create_characterstring_value_t; +typedef struct { + uint32_t device_bacnet_id; + uint32_t object_bacnet_id; + char name[MAXATOMLEN]; + char description[MAXATOMLEN]; + char active_text[MAXATOMLEN]; + char inactive_text[MAXATOMLEN]; + bool value; + + BACNET_POLARITY polarity; +} create_binary_input_t; + +typedef struct { + uint32_t device_bacnet_id; + uint32_t object_bacnet_id; + bool value; +} set_binary_input_value_t; + int bacnet_call_malloc(bacnet_call_type_t type, void** call); int decode_bacnet_call_type(char* buffer, int* index, bacnet_call_type_t* type); From 60c50ca8babf0dec37903bce7e4c7953050feb3a Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 11 Apr 2025 00:30:55 -0700 Subject: [PATCH 3/5] Do not allow creation/deletion of device objects --- src/bacnet.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bacnet.c b/src/bacnet.c index a0f175e..8638ead 100644 --- a/src/bacnet.c +++ b/src/bacnet.c @@ -263,8 +263,8 @@ static object_functions_t SUPPORTED_OBJECT_TABLE[] = { .Object_Intrinsic_Reporting = NULL, .Object_Add_List_Element = NULL, .Object_Remove_List_Element = NULL, - .Object_Create = Routed_Analog_Input_Create, - .Object_Delete = Routed_Analog_Input_Delete, + .Object_Create = NULL, + .Object_Delete = NULL, .Object_Timer = NULL, }, { @@ -285,8 +285,8 @@ static object_functions_t SUPPORTED_OBJECT_TABLE[] = { .Object_Intrinsic_Reporting = NULL, .Object_Add_List_Element = NULL, .Object_Remove_List_Element = NULL, - .Object_Create = Routed_Multistate_Input_Create, - .Object_Delete = Routed_Multistate_Input_Delete, + .Object_Create = NULL, + .Object_Delete = NULL, .Object_Timer = NULL, }, { From ca0235dd4d7785de1690e64757000ac4b4c4ddd1 Mon Sep 17 00:00:00 2001 From: abelino Date: Fri, 11 Apr 2025 00:33:05 -0700 Subject: [PATCH 4/5] Allow setting description for all objects and bump char limit to 128 --- CMakeLists.txt | 3 +- lib/bacnet/gateway/object.ex | 28 +++-- ...on-and-name-when-creating-input-objs.patch | 103 ++++++++++++++++++ src/bacnet.c | 15 ++- src/object/command.c | 23 +++- src/object/command.h | 4 +- src/protocol/decode_call.c | 8 ++ src/protocol/decode_call.h | 9 +- 8 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 patches/0009-Set-description-and-name-when-creating-input-objs.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index da58d84..01f7978 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,8 @@ else() patches/0005-Add-routed-analog-input-object-support.patch patches/0006-Add-routed-multistate-input-object-support.patch patches/0007-Allow-BACNET_PROTOCOL_REVISION-to-be-set-by-user.patch - patches/0008-Exclude-object-identifier-from-the-common-prop-list.patch) + patches/0008-Exclude-object-identifier-from-the-common-prop-list.patch + patches/0009-Set-description-and-name-when-creating-input-objs.patch) endif() CPMFindPackage( diff --git a/lib/bacnet/gateway/object.ex b/lib/bacnet/gateway/object.ex index 8b92aed..d7448f2 100644 --- a/lib/bacnet/gateway/object.ex +++ b/lib/bacnet/gateway/object.ex @@ -15,18 +15,20 @@ defmodule BACNet.Gateway.Object do - `unit`: The unit of measurement for the analog input, represented as an atom. """ @spec create_analog_input( - pid :: pid, - device_id :: integer, - object_id :: integer, - name :: String.t, - unit :: atom + pid :: pid, + device_id :: integer, + object_id :: integer, + name :: String.t, + description :: String.t, + unit :: atom ) :: :ok | {:error, term} - def create_analog_input(pid, device_id, object_id, name, unit) do + def create_analog_input(pid, device_id, object_id, name, description, unit) do GenServer.call(pid, { :create_routed_analog_input, device_id, object_id, name, + description, unit, }) end @@ -68,18 +70,20 @@ defmodule BACNet.Gateway.Object do - `states`: A set of strings representing the object's states. """ @spec create_multistate_input( - pid :: pid, - device_id :: integer, - object_id :: integer, - name :: String.t, - states :: [String.t] + pid :: pid, + device_id :: integer, + object_id :: integer, + name :: String.t, + description :: String.t, + states :: [String.t] ) :: :ok | {:error, term} - def create_multistate_input(pid, device_id, object_id, name, states) do + def create_multistate_input(pid, device_id, object_id, name, description, states) do GenServer.call(pid, { :create_routed_multistate_input, device_id, object_id, name, + description, states, }) end diff --git a/patches/0009-Set-description-and-name-when-creating-input-objs.patch b/patches/0009-Set-description-and-name-when-creating-input-objs.patch new file mode 100644 index 0000000..3aac192 --- /dev/null +++ b/patches/0009-Set-description-and-name-when-creating-input-objs.patch @@ -0,0 +1,103 @@ +From d0bb51317816d9c3b4a0fc657e0bb7d5fcdc1150 Mon Sep 17 00:00:00 2001 +From: abelino +Date: Thu, 10 Apr 2025 20:23:05 -0700 +Subject: [PATCH] Set description and name when creating a msi and ai objects + +--- + src/bacnet/basic/object/routed_analog_input.c | 10 ++++++++-- + src/bacnet/basic/object/routed_analog_input.h | 5 ++++- + src/bacnet/basic/object/routed_multistate_input.c | 10 ++++++++-- + src/bacnet/basic/object/routed_multistate_input.h | 5 ++++- + 4 files changed, 24 insertions(+), 6 deletions(-) + +diff --git a/src/bacnet/basic/object/routed_analog_input.c b/src/bacnet/basic/object/routed_analog_input.c +index 6064161e..d0cb3ff7 100644 +--- a/src/bacnet/basic/object/routed_analog_input.c ++++ b/src/bacnet/basic/object/routed_analog_input.c +@@ -336,8 +336,11 @@ void Routed_Analog_Input_Change_Of_Value_Clear(uint32_t instance_number) + object->Changed = false; + } + +-uint32_t Routed_Analog_Input_Create(uint32_t object_instance) +-{ ++uint32_t Routed_Analog_Input_Create( ++ uint32_t object_instance, ++ char *name, ++ char *description ++) { + DEVICE_OBJECT_DATA *device = Get_Routed_Device_Object(-1); + + if (object_instance >= BACNET_MAX_INSTANCE) +@@ -369,6 +372,9 @@ uint32_t Routed_Analog_Input_Create(uint32_t object_instance) + memset(object->Object_Name, 0, sizeof(object->Object_Name)); + memset(object->Description, 0, sizeof(object->Description)); + ++ memcpy(object->Object_Name, name, strlen(name)); ++ memcpy(object->Description, description, strlen(description)); ++ + if (Keylist_Data_Add(device->objects, object_instance, object) < 0) { + free(object); + return BACNET_MAX_INSTANCE; +diff --git a/src/bacnet/basic/object/routed_analog_input.h b/src/bacnet/basic/object/routed_analog_input.h +index 63ef9b16..399e4777 100644 +--- a/src/bacnet/basic/object/routed_analog_input.h ++++ b/src/bacnet/basic/object/routed_analog_input.h +@@ -85,7 +85,10 @@ BACNET_STACK_EXPORT + void Routed_Analog_Input_Intrinsic_Reporting(uint32_t object_instance); + + BACNET_STACK_EXPORT +-uint32_t Routed_Analog_Input_Create(uint32_t object_instance); ++uint32_t Routed_Analog_Input_Create( ++ uint32_t object_instance, ++ char *name, ++ char *description); + + BACNET_STACK_EXPORT + bool Routed_Analog_Input_Delete(uint32_t object_instance); +diff --git a/src/bacnet/basic/object/routed_multistate_input.c b/src/bacnet/basic/object/routed_multistate_input.c +index 9df2251f..4301e0da 100644 +--- a/src/bacnet/basic/object/routed_multistate_input.c ++++ b/src/bacnet/basic/object/routed_multistate_input.c +@@ -435,8 +435,11 @@ void Routed_Multistate_Input_Change_Of_Value_Clear(uint32_t instance_number) + object->Changed = false; + } + +-uint32_t Routed_Multistate_Input_Create(uint32_t object_instance) +-{ ++uint32_t Routed_Multistate_Input_Create( ++ uint32_t object_instance, ++ char *name, ++ char *description ++) { + DEVICE_OBJECT_DATA *device = Get_Routed_Device_Object(-1); + + if (object_instance >= BACNET_MAX_INSTANCE) +@@ -465,6 +468,9 @@ uint32_t Routed_Multistate_Input_Create(uint32_t object_instance) + memset(object->Object_Name, 0, sizeof(object->Object_Name)); + memset(object->Description, 0, sizeof(object->Description)); + ++ memcpy(object->Object_Name, name, strlen(name)); ++ memcpy(object->Description, description, strlen(description)); ++ + if (Keylist_Data_Add(device->objects, object_instance, object) < 0) { + free(object); + return BACNET_MAX_INSTANCE; +diff --git a/src/bacnet/basic/object/routed_multistate_input.h b/src/bacnet/basic/object/routed_multistate_input.h +index d9a5725f..3e73b34d 100644 +--- a/src/bacnet/basic/object/routed_multistate_input.h ++++ b/src/bacnet/basic/object/routed_multistate_input.h +@@ -81,7 +81,10 @@ BACNET_STACK_EXPORT + void Routed_Multistate_Input_Intrinsic_Reporting(uint32_t object_instance); + + BACNET_STACK_EXPORT +-uint32_t Routed_Multistate_Input_Create(uint32_t object_instance); ++uint32_t Routed_Multistate_Input_Create( ++ uint32_t object_instance, ++ char *name, ++ char *description); + + BACNET_STACK_EXPORT + bool Routed_Multistate_Input_Delete(uint32_t object_instance); +-- +2.48.1 + diff --git a/src/bacnet.c b/src/bacnet.c index 8638ead..37643bc 100644 --- a/src/bacnet.c +++ b/src/bacnet.c @@ -510,7 +510,12 @@ handle_create_routed_analog_input(create_routed_analog_input_t* params) Routed_Device_Instance_To_Index(params->device_bacnet_id); Get_Routed_Device_Object(device_index); - Routed_Analog_Input_Create(params->object_bacnet_id); + Routed_Analog_Input_Create( + params->object_bacnet_id, + params->name, + params->description + ); + Routed_Analog_Input_Units_Set(params->object_bacnet_id, params->unit); Routed_Analog_Input_Name_Set(params->object_bacnet_id, params->name); Get_Routed_Device_Object(0); @@ -542,8 +547,12 @@ handle_create_routed_multistate_input(create_routed_multistate_input_t* params) Routed_Device_Instance_To_Index(params->device_bacnet_id); Get_Routed_Device_Object(device_index); - Routed_Multistate_Input_Create(params->object_bacnet_id); - Routed_Multistate_Input_Name_Set(params->object_bacnet_id, params->name); + + Routed_Multistate_Input_Create( + params->object_bacnet_id, + params->name, + params->description + ); Routed_Multistate_Input_State_Text_List_Set( params->object_bacnet_id, diff --git a/src/object/command.c b/src/object/command.c index aa7b3fd..a357e74 100644 --- a/src/object/command.c +++ b/src/object/command.c @@ -188,6 +188,21 @@ bool command_name_set(uint32_t instance, char *name) return true; } +/** + * @brief Retrieve the description of a Command Object. + * + * @param[in] object - The Command Object. + * @param[out] description - The Objects's description. + */ +bool command_description( + COMMAND_OBJECT* object, + BACNET_CHARACTER_STRING* description +) { + if (strlen(object->description) <= 0) return false; + + return characterstring_init_ansi(description, object->description); +} + /** * @brief Set the present value of a Command Object. * @@ -220,7 +235,7 @@ bool command_update_status(COMMAND_OBJECT* object, bool successful) object->successful = successful; return true; - } +} /** * @brief BACnet read-property handler for a Command Object. @@ -258,6 +273,12 @@ int command_read_property(BACNET_READ_PROPERTY_DATA* data) apdu_len = encode_application_character_string(&apdu[0], &name); break; + case PROP_DESCRIPTION: + BACNET_CHARACTER_STRING description; + command_description(object, &description); + apdu_len = encode_application_character_string(&apdu[0], &description); + break; + case PROP_OBJECT_TYPE: apdu_len = encode_application_enumerated(&apdu[0], OBJECT_COMMAND); break; diff --git a/src/object/command.h b/src/object/command.h index 66fcf25..6f985e3 100644 --- a/src/object/command.h +++ b/src/object/command.h @@ -7,8 +7,8 @@ #define MAX_COMMAND_ACTIONS 8 #endif -#define MAX_OBJ_NAME_LEN 32 -#define MAX_OBJ_DESC_LEN 64 +#define MAX_OBJ_NAME_LEN 128 +#define MAX_OBJ_DESC_LEN 128 typedef struct { BACNET_OBJECT_TYPE type; diff --git a/src/protocol/decode_call.c b/src/protocol/decode_call.c index 21c834d..81de394 100644 --- a/src/protocol/decode_call.c +++ b/src/protocol/decode_call.c @@ -195,6 +195,10 @@ static int decode_create_routed_analog_input( || (size >= sizeof(data->name)) || (memset(data->name, 0, sizeof(data->name)) == NULL) || ei_decode_binary(buffer, index, data->name, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->description)) + || (memset(data->description, 0, sizeof(data->description)) == NULL) + || ei_decode_binary(buffer, index, data->description, &size) || decode_bacnet_unit_atom(buffer, index, &data->unit); return is_invalid ? -1 : 0; @@ -270,6 +274,10 @@ static int decode_create_routed_multistate_input( || (size >= sizeof(data->name)) || (memset(data->name, 0, sizeof(data->name)) == NULL) || ei_decode_binary(buffer, index, data->name, &size) + || ei_get_type(buffer, index, &type, (int*)&size) + || (size >= sizeof(data->description)) + || (memset(data->description, 0, sizeof(data->description)) == NULL) + || ei_decode_binary(buffer, index, data->description, &size) || decode_multistate_states(buffer, index, &data->states, &data->states_length); return is_invalid ? -1 : 0; diff --git a/src/protocol/decode_call.h b/src/protocol/decode_call.h index 9c997e8..a820d68 100644 --- a/src/protocol/decode_call.h +++ b/src/protocol/decode_call.h @@ -32,9 +32,11 @@ typedef struct { } create_routed_device_t; typedef struct { - uint32_t device_bacnet_id; - uint32_t object_bacnet_id; - char name[MAXATOMLEN]; + uint32_t device_bacnet_id; + uint32_t object_bacnet_id; + char name[MAXATOMLEN]; + char description[MAXATOMLEN]; + BACNET_ENGINEERING_UNITS unit; } create_routed_analog_input_t; @@ -48,6 +50,7 @@ typedef struct { uint32_t device_bacnet_id; uint32_t object_bacnet_id; char name[MAXATOMLEN]; + char description[MAXATOMLEN]; char* states; size_t states_length; } create_routed_multistate_input_t; From 6c0390cd7d56ea9dc2a08eef8bf64a75df7ab4e8 Mon Sep 17 00:00:00 2001 From: abelino Date: Sat, 12 Apr 2025 00:40:03 -0700 Subject: [PATCH 5/5] Add docs and increase length of all string values to MAXATOMLEN --- lib/bacnet/gateway/object.ex | 71 ++++++++++++++++++++++++++++-- src/object/binary_input.c | 41 ++++++++++++++--- src/object/binary_input.h | 13 +++--- src/object/characterstring_value.c | 56 ++++++++++++++--------- src/object/characterstring_value.h | 20 ++------- src/object/command.h | 9 ++-- src/object/common.h | 8 ++++ 7 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 src/object/common.h diff --git a/lib/bacnet/gateway/object.ex b/lib/bacnet/gateway/object.ex index d7448f2..dd1a373 100644 --- a/lib/bacnet/gateway/object.ex +++ b/lib/bacnet/gateway/object.ex @@ -12,6 +12,7 @@ defmodule BACNet.Gateway.Object do - `device_id`: The ID of the BACnet device where the object will be created. - `object_id`: The unique ID for the new analog input object. - `name`: A unique name for the object. + - `description`: A short description for the analog input object. - `unit`: The unit of measurement for the analog input, represented as an atom. """ @spec create_analog_input( @@ -44,10 +45,10 @@ defmodule BACNet.Gateway.Object do - `value`: The new present value to be set for the analog input. """ @spec set_analog_input_present_value( - pid :: pid, + pid :: pid, device_id :: integer, object_id :: integer, - value :: float + value :: float ) :: :ok | {:error, term} def set_analog_input_present_value(pid, device_id, object_id, value) do GenServer.call(pid, { @@ -67,6 +68,7 @@ defmodule BACNet.Gateway.Object do - `device_id`: The ID of the BACnet device where the object will be created. - `object_id`: The unique ID for the new multistate input object. - `name`: A unique name for the object. + - `description`: A short description for the multistate input object. - `states`: A set of strings representing the object's states. """ @spec create_multistate_input( @@ -123,6 +125,7 @@ defmodule BACNet.Gateway.Object do - `object_id`: The unique ID for the new command object. - `name`: A unique name for the object. - `description`: A short description for the command object. + - `value`: Set the initial value when a value is provided. """ @spec create_command( pid :: pid, @@ -157,7 +160,7 @@ defmodule BACNet.Gateway.Object do pid :: pid, device_id :: integer, object_id :: integer, - status :: :succeeded | :failed + status :: :succeeded | :failed ) :: :ok | {:error, term} def set_command_status(pid, device_id, object_id, status) do GenServer.call(pid, { @@ -168,6 +171,26 @@ defmodule BACNet.Gateway.Object do }) end + @doc """ + Creates a new characterstring value object. + + ## Parameters + + - `pid`: The PID of the GenServer managing BACnet communications. + - `device_id`: The ID of the BACnet device where the object will be created. + - `object_id`: The unique ID for the new characterstring value object. + - `name`: A unique name for the object. + - `description`: A short description for the characterstring value object. + - `value`: A utf-8 string. + """ + @spec create_characterstring_value( + pid :: pid, + device_id :: integer, + object_id :: integer, + name :: String.t, + description :: String.t, + value :: String.t + ) :: :ok | {:error, term} def create_characterstring_value(pid, device_id, object_id, name, description, value) do GenServer.call(pid, { :create_characterstring_value, @@ -179,6 +202,32 @@ defmodule BACNet.Gateway.Object do }) end + @doc """ + Creates a new binary input object. + + ## Parameters + + - `pid`: The PID of the GenServer managing BACnet communications. + - `device_id`: The ID of the BACnet device where the object will be created. + - `object_id`: The unique ID for the new binary input object. + - `name`: A unique name for the object. + - `description`: A short description for the binary input object. + - `active_test`: A short description representing the active state of value. + - `inactive_test`: A short description representing the inactive state of value. + - `polarity`: How the physical state of an input corresponds to the logical state of value. + - `value`: A binary input value represented as a boolean. + """ + @spec create_binary_input( + pid :: pid, + device_id :: integer, + object_id :: integer, + name :: String.t, + description :: String.t, + active_text :: String.t, + inactive_text :: String.t, + polarity :: :normal | :reverse | :max, + value :: boolean + ) :: :ok | {:error, term} def create_binary_input(pid, device_id, object_id, name, description, active_text, inactive_text, polarity, value) do GenServer.call(pid, { :create_binary_input, @@ -193,6 +242,22 @@ defmodule BACNet.Gateway.Object do }) end + @doc """ + Set the value of a binary input object. + + ## Parameters + + - `pid`: The PID of the GenServer managing BACnet communications. + - `device_id`: The ID of the BACnet device where the object will be created. + - `object_id`: The unique ID for the new command object. + - `value`: A binary input value represented as a boolean. + """ + @spec set_binary_input_present_value( + pid :: pid, + device_id :: integer, + object_id :: integer, + value :: boolean + ) :: :ok | {:error, term} def set_binary_input_present_value(pid, device_id, object_id, value) do GenServer.call(pid, { :set_binary_input_value, diff --git a/src/object/binary_input.c b/src/object/binary_input.c index 9d5f079..80c4706 100644 --- a/src/object/binary_input.c +++ b/src/object/binary_input.c @@ -1,13 +1,8 @@ #include -#include -#include -#include #include #include -#include #include "object/binary_input.h" -#include "protocol/event.h" static const int required_properties[] = { PROP_OBJECT_IDENTIFIER, @@ -124,6 +119,11 @@ uint32_t binary_input_index_to_instance(unsigned index) { return key; } +/** + * @brief Checks if the Object instance is a valid binary-input Object. + * + * @param instance - Object instance number. + */ bool binary_input_valid_instance(uint32_t instance) { DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); @@ -134,6 +134,12 @@ bool binary_input_valid_instance(uint32_t instance) { return true; } +/** + * @brief Retrieve the name of a binary-input Object. + * + * @param[in] instance - Object instance number. + * @param[out] name - The Objects's name. + */ bool binary_input_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); BINARY_INPUT_OBJECT* object = Keylist_Data(device->objects, instance); @@ -144,6 +150,12 @@ bool binary_input_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { return characterstring_init_ansi(name, object->name); } +/** + * @brief Retrieve the description of a binary-input Object. + * + * @param[in] object - A binary-input Object. + * @param[out] description - The Objects's description. + */ bool binary_input_description( BINARY_INPUT_OBJECT* object, BACNET_CHARACTER_STRING* description @@ -153,6 +165,12 @@ bool binary_input_description( return characterstring_init_ansi(description, object->description); } +/** + * @brief Retrieve the active-text value of a binary-input Object. + * + * @param[in] object - A binary-input Object. + * @param[out] active_text - The Objects's active-text value. + */ bool binary_input_active_text( BINARY_INPUT_OBJECT* object, BACNET_CHARACTER_STRING* active_text @@ -162,6 +180,12 @@ bool binary_input_active_text( return characterstring_init_ansi(active_text, object->active_text); } +/** + * @brief Retrieve the inactive-text value of a binary-input Object. + * + * @param[in] object - A binary-input Object. + * @param[out] inactive_text - The Objects's inactive-text value. + */ bool binary_input_inactive_text( BINARY_INPUT_OBJECT* object, BACNET_CHARACTER_STRING* inactive_text @@ -171,6 +195,13 @@ bool binary_input_inactive_text( return characterstring_init_ansi(inactive_text, object->inactive_text); } +/** + * @brief BACnet read-property handler for binary-input Object. + * + * @param[out] data - Holds request and reply data. + * + * @return Byte count of the APDU or BACNET_STATUS_ERROR. + */ int binary_input_read_property(BACNET_READ_PROPERTY_DATA* data) { bool is_data_invalid = diff --git a/src/object/binary_input.h b/src/object/binary_input.h index 4b1a730..5e1d4cd 100644 --- a/src/object/binary_input.h +++ b/src/object/binary_input.h @@ -1,18 +1,15 @@ #ifndef BACNET_OBJECT_BINARY_INPUT_H #define BACNET_OBJECT_BINARY_INPUT_H -#include - -#define MAX_OBJ_NAME_LEN 128 -#define MAX_OBJ_DESC_LEN 128 +#include "object/common.h" typedef struct { BACNET_OBJECT_TYPE type; - char name[MAX_OBJ_NAME_LEN]; - char description[MAX_OBJ_DESC_LEN]; - char active_text[MAX_OBJ_NAME_LEN]; - char inactive_text[MAX_OBJ_NAME_LEN]; + char name[MAX_STRING_LEN]; + char description[MAX_STRING_LEN]; + char active_text[MAX_STRING_LEN]; + char inactive_text[MAX_STRING_LEN]; bool present_value; BACNET_POLARITY polarity; diff --git a/src/object/characterstring_value.c b/src/object/characterstring_value.c index b2a18f4..eeba9ca 100644 --- a/src/object/characterstring_value.c +++ b/src/object/characterstring_value.c @@ -1,13 +1,8 @@ #include -#include -#include -#include #include #include -#include #include "object/characterstring_value.h" -#include "protocol/event.h" static const int required_properties[] = { PROP_OBJECT_IDENTIFIER, @@ -122,6 +117,12 @@ uint32_t characterstring_value_index_to_instance(unsigned index) { return key; } +/** + * @brief Checks if the Object instance is a valid character-string value + * Object. + * + * @param instance - Object instance number. + */ bool characterstring_value_valid_instance(uint32_t instance) { DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); @@ -134,6 +135,12 @@ bool characterstring_value_valid_instance(uint32_t instance) { return true; } +/** + * @brief Retrieve the name of a character-string Object. + * + * @param[in] instance - Object instance number. + * @param[out] name - The Objects's name. + */ bool characterstring_value_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); @@ -147,36 +154,43 @@ characterstring_value_name(uint32_t instance, BACNET_CHARACTER_STRING* name) { return characterstring_init_ansi(name, object->name); } +/** + * @brief Retrieve the description of a character-string Object. + * + * @param[in] object - A character-string Object. + * @param[out] description - The Objects's description. + */ bool characterstring_value_description( - uint32_t instance, + CHARACTERSTRING_VALUE_OBJECT* object, BACNET_CHARACTER_STRING* description ) { - DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); - - CHARACTERSTRING_VALUE_OBJECT* object = - Keylist_Data(device->objects, instance); - - if (object == NULL) return false; if (strlen(object->description) <= 0) return false; return characterstring_init_ansi(description, object->description); } +/** + * @brief Retrieve the present-value of a character-string Object. + * + * @param[in] object - A character-string Object. + * @param[out] value - The Objects's present-value. + */ bool characterstring_value_present_value( - uint32_t instance, + CHARACTERSTRING_VALUE_OBJECT* object, BACNET_CHARACTER_STRING* value ) { - DEVICE_OBJECT_DATA* device = Get_Routed_Device_Object(-1); - - CHARACTERSTRING_VALUE_OBJECT* object = - Keylist_Data(device->objects, instance); - - if (object == NULL) return false; if (strlen(object->present_value) <= 0) return false; return characterstring_init_ansi(value, object->present_value); } +/** + * @brief BACnet read-property handler for character-string Object. + * + * @param[out] data - Holds request and reply data. + * + * @return Byte count of the APDU or BACNET_STATUS_ERROR. + */ int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data) { bool is_data_invalid = @@ -218,7 +232,7 @@ int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data) case PROP_DESCRIPTION: BACNET_CHARACTER_STRING description; - characterstring_value_description(data->object_instance, &description); + characterstring_value_description(object, &description); apdu_len = encode_application_character_string(&apdu[0], &description); break; @@ -229,7 +243,7 @@ int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data) case PROP_PRESENT_VALUE: BACNET_CHARACTER_STRING present_value; - characterstring_value_present_value(data->object_instance, &present_value); + characterstring_value_present_value(object, &present_value); apdu_len = encode_application_character_string(&apdu[0], &present_value); break; diff --git a/src/object/characterstring_value.h b/src/object/characterstring_value.h index 4febf8a..6386bc0 100644 --- a/src/object/characterstring_value.h +++ b/src/object/characterstring_value.h @@ -1,18 +1,14 @@ #ifndef BACNET_OBJECT_CHARACTERSTRING_VALUE_H #define BACNET_OBJECT_CHARACTERSTRING_VALUE_H -#include - -#define MAX_OBJ_NAME_LEN 128 -#define MAX_OBJ_DESC_LEN 128 -#define MAX_OBJ_VALUE_LEN 128 +#include "object/common.h" typedef struct { BACNET_OBJECT_TYPE type; - char name[MAX_OBJ_NAME_LEN]; - char description[MAX_OBJ_DESC_LEN]; - char present_value[MAX_OBJ_VALUE_LEN]; + char name[MAX_STRING_LEN]; + char description[MAX_STRING_LEN]; + char present_value[MAX_STRING_LEN]; } CHARACTERSTRING_VALUE_OBJECT; void characterstring_value_init(void); @@ -32,14 +28,6 @@ bool characterstring_value_name( uint32_t instance, BACNET_CHARACTER_STRING* name); -bool characterstring_value_description( - uint32_t instance, - BACNET_CHARACTER_STRING* description); - -bool characterstring_value_present_value( - uint32_t instance, - BACNET_CHARACTER_STRING* value); - int characterstring_value_read_property(BACNET_READ_PROPERTY_DATA* data); void characterstring_value_property_lists( diff --git a/src/object/command.h b/src/object/command.h index 6f985e3..a9e02dd 100644 --- a/src/object/command.h +++ b/src/object/command.h @@ -3,21 +3,20 @@ #include +#include "object/common.h" + #ifndef MAX_COMMAND_ACTIONS #define MAX_COMMAND_ACTIONS 8 #endif -#define MAX_OBJ_NAME_LEN 128 -#define MAX_OBJ_DESC_LEN 128 - typedef struct { BACNET_OBJECT_TYPE type; uint32_t present_value; bool in_progress; bool successful; - char name[MAX_OBJ_NAME_LEN]; - char description[MAX_OBJ_DESC_LEN]; + char name[MAX_STRING_LEN]; + char description[MAX_STRING_LEN]; BACNET_ACTION_LIST actions[MAX_COMMAND_ACTIONS]; } COMMAND_OBJECT; diff --git a/src/object/common.h b/src/object/common.h new file mode 100644 index 0000000..4774e9d --- /dev/null +++ b/src/object/common.h @@ -0,0 +1,8 @@ +#ifndef BACNET_OBJECT_PROPERTIES_H +#define BACNET_OBJECT_PROPERTIES_H + +#include + +#define MAX_STRING_LEN MAXATOMLEN + +#endif /* BACNET_OBJECT_PROPERTIES_H */