From 3329dff3371ef1ca8122b46b2f0f52763fa3d286 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 25 Oct 2024 10:43:29 -0500 Subject: [PATCH] Added WriteGroup service and Channel object interfaces (#829) --- CMakeLists.txt | 11 + Makefile | 4 + apps/Makefile | 6 +- apps/server/main.c | 22 +- apps/writegroup/Makefile | 39 + apps/writegroup/main.c | 207 +++ src/bacnet/bacapp.c | 428 +++++- src/bacnet/bacapp.h | 8 + src/bacnet/bacenum.h | 4 +- src/bacnet/basic/object/channel.c | 1291 +++++++---------- src/bacnet/basic/object/channel.h | 110 +- src/bacnet/basic/service/h_write_group.c | 111 ++ src/bacnet/basic/service/h_write_group.h | 36 + src/bacnet/basic/service/s_write_group.c | 62 + src/bacnet/basic/service/s_write_group.h | 30 + src/bacnet/basic/services.h | 2 + src/bacnet/channel_value.c | 1053 ++++++++++++++ src/bacnet/channel_value.h | 146 ++ src/bacnet/config.h | 5 +- src/bacnet/write_group.c | 700 +++++++++ src/bacnet/write_group.h | 154 ++ test/CMakeLists.txt | 4 +- test/bacnet/access_rule/CMakeLists.txt | 1 + test/bacnet/bacapp/CMakeLists.txt | 1 + test/bacnet/bacdest/CMakeLists.txt | 1 + test/bacnet/bacdevobjpropref/CMakeLists.txt | 1 + test/bacnet/bactimevalue/CMakeLists.txt | 1 + .../basic/binding/address/CMakeLists.txt | 1 + test/bacnet/basic/object/acc/CMakeLists.txt | 1 + .../object/access_credential/CMakeLists.txt | 1 + .../basic/object/access_door/CMakeLists.txt | 1 + .../basic/object/access_point/CMakeLists.txt | 1 + .../basic/object/access_rights/CMakeLists.txt | 1 + .../basic/object/access_user/CMakeLists.txt | 1 + .../basic/object/access_zone/CMakeLists.txt | 1 + test/bacnet/basic/object/ai/CMakeLists.txt | 1 + test/bacnet/basic/object/ao/CMakeLists.txt | 1 + test/bacnet/basic/object/av/CMakeLists.txt | 1 + .../basic/object/bacfile/CMakeLists.txt | 1 + test/bacnet/basic/object/bi/CMakeLists.txt | 1 + .../object/bitstring_value/CMakeLists.txt | 1 + test/bacnet/basic/object/blo/CMakeLists.txt | 1 + test/bacnet/basic/object/bo/CMakeLists.txt | 1 + test/bacnet/basic/object/bv/CMakeLists.txt | 1 + .../basic/object/calendar/CMakeLists.txt | 1 + .../basic/object/channel/CMakeLists.txt | 6 + test/bacnet/basic/object/channel/src/main.c | 299 ++-- .../basic/object/color_object/CMakeLists.txt | 1 + .../object/color_temperature/CMakeLists.txt | 1 + .../basic/object/command/CMakeLists.txt | 1 + .../credential_data_input/CMakeLists.txt | 1 + test/bacnet/basic/object/csv/CMakeLists.txt | 1 + .../bacnet/basic/object/device/CMakeLists.txt | 1 + test/bacnet/basic/object/iv/CMakeLists.txt | 1 + test/bacnet/basic/object/lc/CMakeLists.txt | 1 + test/bacnet/basic/object/lo/CMakeLists.txt | 1 + test/bacnet/basic/object/lsp/CMakeLists.txt | 1 + test/bacnet/basic/object/lsz/CMakeLists.txt | 1 + .../basic/object/ms-input/CMakeLists.txt | 1 + test/bacnet/basic/object/mso/CMakeLists.txt | 1 + test/bacnet/basic/object/msv/CMakeLists.txt | 1 + test/bacnet/basic/object/nc/CMakeLists.txt | 1 + .../basic/object/netport/CMakeLists.txt | 2 + test/bacnet/basic/object/osv/CMakeLists.txt | 1 + test/bacnet/basic/object/piv/CMakeLists.txt | 1 + .../basic/object/schedule/CMakeLists.txt | 1 + .../object/structured_view/CMakeLists.txt | 1 + .../basic/object/time_value/CMakeLists.txt | 1 + .../basic/object/trendlog/CMakeLists.txt | 1 + test/bacnet/channel_value/CMakeLists.txt | 50 + test/bacnet/channel_value/src/main.c | 147 ++ test/bacnet/cov/CMakeLists.txt | 1 + test/bacnet/create_object/CMakeLists.txt | 1 + test/bacnet/delete_object/CMakeLists.txt | 1 + test/bacnet/event/CMakeLists.txt | 1 + test/bacnet/getalarm/CMakeLists.txt | 1 + test/bacnet/getevent/CMakeLists.txt | 1 + test/bacnet/hostnport/CMakeLists.txt | 1 + test/bacnet/list_element/CMakeLists.txt | 1 + test/bacnet/lso/CMakeLists.txt | 1 + test/bacnet/ptransfer/CMakeLists.txt | 1 + test/bacnet/rpm/CMakeLists.txt | 1 + test/bacnet/specialevent/CMakeLists.txt | 2 +- test/bacnet/timesync/CMakeLists.txt | 1 + test/bacnet/weeklyschedule/CMakeLists.txt | 1 + test/bacnet/wp/CMakeLists.txt | 1 + test/bacnet/wpm/CMakeLists.txt | 1 + test/bacnet/write_group/CMakeLists.txt | 66 + test/bacnet/write_group/src/main.c | 292 ++++ 89 files changed, 4334 insertions(+), 1022 deletions(-) create mode 100644 apps/writegroup/Makefile create mode 100644 apps/writegroup/main.c create mode 100644 src/bacnet/basic/service/h_write_group.c create mode 100644 src/bacnet/basic/service/h_write_group.h create mode 100644 src/bacnet/basic/service/s_write_group.c create mode 100644 src/bacnet/basic/service/s_write_group.h create mode 100644 src/bacnet/channel_value.c create mode 100644 src/bacnet/channel_value.h create mode 100644 src/bacnet/write_group.c create mode 100644 src/bacnet/write_group.h create mode 100644 test/bacnet/channel_value/CMakeLists.txt create mode 100644 test/bacnet/channel_value/src/main.c create mode 100644 test/bacnet/write_group/CMakeLists.txt create mode 100644 test/bacnet/write_group/src/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index af8a04b9..a3c2f63f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -249,6 +249,8 @@ add_library(${PROJECT_NAME} src/bacnet/bactext.h src/bacnet/bactimevalue.c src/bacnet/bactimevalue.h + src/bacnet/channel_value.c + src/bacnet/channel_value.h src/bacnet/dailyschedule.c src/bacnet/dailyschedule.h src/bacnet/weeklyschedule.c @@ -418,6 +420,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/service/h_wp.h src/bacnet/basic/service/h_wpm.c src/bacnet/basic/service/h_wpm.h + src/bacnet/basic/service/h_write_group.c + src/bacnet/basic/service/h_write_group.h src/bacnet/basic/service/s_abort.c src/bacnet/basic/service/s_abort.h src/bacnet/basic/service/s_ack_alarm.c @@ -474,6 +478,8 @@ add_library(${PROJECT_NAME} src/bacnet/basic/service/s_wp.h src/bacnet/basic/service/s_wpm.c src/bacnet/basic/service/s_wpm.h + src/bacnet/basic/service/s_write_group.c + src/bacnet/basic/service/s_write_group.h src/bacnet/basic/services.h src/bacnet/basic/sys/bigend.c src/bacnet/basic/sys/bigend.h @@ -594,6 +600,8 @@ add_library(${PROJECT_NAME} src/bacnet/wp.h src/bacnet/wpm.c src/bacnet/wpm.h + src/bacnet/write_group.c + src/bacnet/write_group.h $<$:src/bacnet/basic/ucix/ucix.c> $<$:src/bacnet/basic/ucix/ucix.h>) target_sources( @@ -1001,6 +1009,9 @@ if(BACNET_STACK_BUILD_APPS) add_executable(writefile apps/writefile/main.c) target_link_libraries(writefile PRIVATE ${PROJECT_NAME}) + add_executable(writegroup apps/writegroup/main.c) + target_link_libraries(writegroup PRIVATE ${PROJECT_NAME}) + add_executable(writeprop apps/writeprop/main.c) target_link_libraries(writeprop PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index 535deab9..25e9e6fa 100644 --- a/Makefile +++ b/Makefile @@ -204,6 +204,10 @@ whois: writepropm: $(MAKE) -s -C apps $@ +.PHONY: writegroup +writegroup: + $(MAKE) -s -C apps $@ + .PHONY: router router: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 77f98554..0351418d 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -206,7 +206,7 @@ SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \ whohas whois iam ucov scov timesync epics readpropm readrange \ writepropm uptransfer getevent uevent abort error event ack-alarm \ server-client add-list-element remove-list-element create-object \ - delete-object server-discover apdu + delete-object server-discover apdu writegroup ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis @@ -458,3 +458,7 @@ fuzz-afl: $(BACNET_LIB_TARGET) .PHONY: writepropm writepropm: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ + +.PHONY: writegroup +writegroup: $(BACNET_LIB_TARGET) + $(MAKE) -B -C $@ diff --git a/apps/server/main.c b/apps/server/main.c index f92a86bc..f677a7ef 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -37,6 +37,7 @@ /* objects that have tasks inside them */ #if (BACNET_PROTOCOL_REVISION >= 14) #include "bacnet/basic/object/lo.h" +#include "bacnet/basic/object/channel.h" #endif #if (BACNET_PROTOCOL_REVISION >= 24) #include "bacnet/basic/object/color_object.h" @@ -117,6 +118,7 @@ static BACNET_SUBORDINATE_DATA Lighting_Subordinate[] = { { 0, OBJECT_COLOR_TEMPERATURE, 1, "color-temperature", 0, 0, NULL }, #endif }; +static BACNET_WRITE_GROUP_NOTIFICATION Write_Group_Notification = { 0 }; /** * @brief Update the strcutured view static data with device ID and linked lists @@ -177,21 +179,6 @@ static void Init_Service_Handlers(void) /* we need to handle who-is to support dynamic device binding */ apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has); - -#if 0 - /* BACnet Testing Observed Incident oi00107 - Server only devices should not indicate that they EXECUTE I-Am - Revealed by BACnet Test Client v1.8.16 ( www.bac-test.com/bacnet-test-client-download ) - BITS: BIT00040 - Any discussions can be directed to edward@bac-test.com - Please feel free to remove this comment when my changes accepted after suitable time for - review by all interested parties. Say 6 months -> September 2016 */ - /* In this demo, we are the server only ( BACnet "B" device ) so we do not indicate - that we can execute the I-Am message */ - /* handle i-am to support binding to other devices */ - apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); -#endif - /* set the handler for all the services we don't implement */ /* It is required to send the proper reject message... */ apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); @@ -231,6 +218,11 @@ static void Init_Service_Handlers(void) apdu_set_unconfirmed_handler( SERVICE_UNCONFIRMED_PRIVATE_TRANSFER, handler_unconfirmed_private_transfer); + apdu_set_unconfirmed_handler( + SERVICE_UNCONFIRMED_WRITE_GROUP, handler_write_group); + /* add WriteGroup iterator to the Channel objects */ + Write_Group_Notification.callback = Channel_Write_Group; + handler_write_group_notification_add(&Write_Group_Notification); #if defined(INTRINSIC_REPORTING) apdu_set_confirmed_handler( SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM, handler_alarm_ack); diff --git a/apps/writegroup/Makefile b/apps/writegroup/Makefile new file mode 100644 index 00000000..7015981c --- /dev/null +++ b/apps/writegroup/Makefile @@ -0,0 +1,39 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacwg +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend diff --git a/apps/writegroup/main.c b/apps/writegroup/main.c new file mode 100644 index 00000000..de4755ad --- /dev/null +++ b/apps/writegroup/main.c @@ -0,0 +1,207 @@ +/** + * @file + * @brief command line tool that sends a BACnet WriteGroup-Request message + * to the network + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include /* for time */ +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bactext.h" +#include "bacnet/iam.h" +#include "bacnet/cov.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/channel_value.h" +#include "bacnet/write_group.h" +/* some demo stuff needed */ +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +static BACNET_WRITE_GROUP_DATA Write_Group_Data; + +static void Init_Service_Handlers(void) +{ + Device_Init(NULL); + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* set the handler for all the services we don't implement + It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* we must implement read property - it's required! */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); +} + +static void print_usage(const char *filename) +{ + printf("Sends a BACnet WriteGroup-Reqeust to the network.\n"); + printf("\n"); + printf( + "Usage: %s group-number priority \n" + "change-value [change-value]\n", + filename); + printf("\n"); + printf("group-number:\n" + "parameter in the range 1-4294967295 that represents\n" + " the control group to be affected by this request.\n"); + printf("\n"); + printf("priority:\n" + "This Write_Priority parameter is an unsigned integer\n" + "in the range 1..16 that represents the priority for writing\n" + "that shall apply to any channel value changes that result\n" + "in writes to properties of BACnet objects.\n"); + printf("\n"); + printf("change-value:\n" + "This parameter shall specify a BACnetGroupChannelValue\n" + "consisting of channel number, overridingPriority, value\n" + "tuples representing each channel number whose value is\n" + "to be updated."); + printf("Since List_Of_Object_Property_References can include\n" + "object properties of different data types, the value\n" + "written to Present_Value may be coerced to another datatype.\n" + "The rules governing how these coercions occur are\n" + "defined in the BACnet standard.\n"); + printf("\n"); + printf("change-value: channel number\n"); + printf("Channel numbers shall range from 0 to 65535\n" + "where the channel number corresponds directly to the\n" + "Channel_Number property of a Channel object."); + printf("\n"); + printf("change-value: overridingPriority\n" + "The optional overridingPriority allows specific values\n" + "to be written with some priority other than that specified\n" + "by Write_Priority property. If overridingPriority 0 is given,\n" + "no priority is sent.\n"); + printf("\n"); + printf("change-value: value\n" + "BACnetChannelValue values that are any primitive application\n" + "datatype or BACnetLightingCommand or BACnetColorCommand or\n" + "BACnetXYColor constructed datatypes. The NULL value represents\n" + "'relinquish control' as with commandable object properties.\n"); + printf("\n"); + printf("The numeric values are parsed in the following manner:\n" + "null=Null, true or false=Boolean,\n" + "numeric with negative sign=Signed Integer,\n" + "numeric with decimal point=Real or Double\n" + "Ltuple=BACnetLightingCommand\n" + "Ctuple=BACnetColorCommand\n" + "Xtuple=BACnetXYColor\n"); + printf("\n"); + printf( + "Example:\n" + "If you want generate a WriteGroup-Request,\n" + "you could send one of the following command:\n" + "%s 1 2 inhibit 3 0 100.0 4 0 null 5 0 -100 6 0 true 7 0 10\n" + "where 1=group-number, 2=priority, 3=channel-number,\n" + "0=overridingPriority, 5=channel-number, 6=channel-number,\n" + "7=channel-number\n", + filename); +} + +/** + * @brief Cleanup the resources used by the application + */ +static void cleanup(void) +{ + unsigned count; + + count = bacnet_write_group_change_list_count(&Write_Group_Data); + while (count) { + BACNET_GROUP_CHANNEL_VALUE *value; + value = + bacnet_write_group_change_list_element(&Write_Group_Data, count); + if (count == 0) { + /* index=0 change-value is static head, not dynamic */ + value->next = NULL; + } else { + free(value); + } + count--; + } +} + +/** + * @brief Send a WriteGroup-Request to the network + * @param argc [in] The number of command line arguments + * @param argv [in] The command line arguments + * @return 0 on success, 1 on failure + */ +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS dest = { 0 /*broadcast*/ }; + BACNET_WRITE_GROUP_DATA *data; + BACNET_GROUP_CHANNEL_VALUE *value; + int argi = 0; + int len = 0; + + if (argc < 4) { + print_usage(filename_remove_path(argv[0])); + return 0; + } + data = &Write_Group_Data; + /* decode the command line parameters */ + data->group_number = strtol(argv[1], NULL, 0); + data->write_priority = strtol(argv[2], NULL, 0); + if (strcasecmp(argv[3], "inhibit") == 0) { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_TRUE; + } else if (strcasecmp(argv[3], "delay") == 0) { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_FALSE; + } else { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_FALSE; + } + value = &data->change_list; + for (argi = 4; argi < argc; argi++) { + if (!value) { + value = calloc(1, sizeof(BACNET_GROUP_CHANNEL_VALUE)); + bacnet_write_group_change_list_append(data, value); + } + if (value) { + value->channel = strtol(argv[argi], NULL, 0); + argi++; + value->overriding_priority = strtol(argv[argi], NULL, 0); + argi++; + if (!bacnet_channel_value_from_ascii(&value->value, argv[argi])) { + value->value.tag = BACNET_APPLICATION_TAG_NULL; + } + printf( + "WriteGroup-Request added channel %u " + "with priority %u value=%s tag=%s\n", + value->channel, value->overriding_priority, argv[argi], + bactext_application_tag_name(value->value.tag)); + value = value->next; + } + } + atexit(cleanup); + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + dlenv_init(); + atexit(datalink_cleanup); + len = Send_Write_Group(&dest, data); + if (len <= 0) { + fprintf( + stderr, "Failed to Send WriteGroup-Request (%s)!\n", + strerror(errno)); + } else { + printf("Send WriteGroup-Request successful!\n"); + } + + return 0; +} diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index 21e0fbb4..4bb0284a 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -33,6 +33,7 @@ #include "bacnet/weeklyschedule.h" #include "bacnet/calendar_entry.h" #include "bacnet/special_event.h" +#include "bacnet/channel_value.h" #include "bacnet/basic/sys/platform.h" #if defined(BACAPP_SCALE) @@ -510,6 +511,13 @@ int bacapp_encode_application_data( apdu_len = bacapp_encode_access_rule(apdu, &value->type.Access_Rule); break; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + case BACNET_APPLICATION_TAG_CHANNEL_VALUE: + /* BACnetChannelValue */ + apdu_len = bacnet_channel_value_type_encode( + apdu, &value->type.Channel_Value); + break; #endif default: break; @@ -1221,6 +1229,9 @@ int bacapp_known_property_tag( (object_type == OBJECT_DATETIME_VALUE)) { /* Properties using BACnetDateTime */ return BACNET_APPLICATION_TAG_DATETIME; + } else if (object_type == OBJECT_CHANNEL) { + /* Properties using BACnetChannelValue */ + return BACNET_APPLICATION_TAG_CHANNEL_VALUE; } /* note: primitive application tagged present-values return '-1' */ return -1; @@ -1607,6 +1618,13 @@ int bacapp_decode_application_tag_value( apdu_len = bacnet_access_rule_decode( apdu, apdu_size, &value->type.Access_Rule); break; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + case BACNET_APPLICATION_TAG_CHANNEL_VALUE: + /* BACnetChannelValue */ + apdu_len = bacnet_channel_value_decode( + apdu, apdu_size, &value->type.Channel_Value); + break; #endif default: break; @@ -2565,6 +2583,255 @@ static int bacapp_snprintf_access_rule( } #endif +#if defined(BACAPP_COLOR_COMMAND) +/** + * @brief Print a value to a string for EPICS + * @param str - destination string, or NULL for length only + * @param str_len - length of the destination string, or 0 for length only + * @param value - value to be printed + * @return number of characters written to the string + */ +static int bacapp_snprintf_color_command( + char *str, size_t str_len, const BACNET_COLOR_COMMAND *value) +{ + int slen; + int ret_val = 0; + + slen = bacapp_snprintf(str, str_len, "{"); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); + slen = bacapp_snprintf(str, str_len, "("); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); + slen = bacapp_snprintf( + str, str_len, "%s", bactext_color_operation_name(value->operation)); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); + slen = bacapp_snprintf(str, str_len, ")"); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); + + /* FIXME: add the Color Command optional values */ + + slen = bacapp_snprintf(str, str_len, "}"); + ret_val += bacapp_snprintf_shift(slen, &str, &str_len); + return ret_val; +} +#endif + +#if defined(BACAPP_CHANNEL_VALUE) +/** + * @brief Print a value to a string for EPICS + * @param str - destination string, or NULL for length only + * @param str_len - length of the destination string, or 0 for length only + * @param value - value to be printed + * @return number of characters written to the string + */ +static int bacapp_snprintf_channel_value( + char *str, size_t str_len, const BACNET_CHANNEL_VALUE *value) +{ + int ret_val = 0; + + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + ret_val = bacapp_snprintf_null(str, str_len); + break; + case BACNET_APPLICATION_TAG_BOOLEAN: +#if defined(CHANNEL_BOOLEAN) + ret_val = + bacapp_snprintf_boolean(str, str_len, value->type.Boolean); +#endif + break; + case BACNET_APPLICATION_TAG_UNSIGNED_INT: +#if defined(CHANNEL_UNSIGNED) + ret_val = bacapp_snprintf_unsigned_integer( + str, str_len, value->type.Unsigned_Int); +#endif + break; + case BACNET_APPLICATION_TAG_SIGNED_INT: +#if defined(CHANNEL_SIGNED) + ret_val = bacapp_snprintf_signed_integer( + str, str_len, value->type.Signed_Int); +#endif + break; + case BACNET_APPLICATION_TAG_REAL: +#if defined(CHANNEL_REAL) + ret_val = bacapp_snprintf_real(str, str_len, value->type.Real); +#endif + break; + case BACNET_APPLICATION_TAG_DOUBLE: +#if defined(CHANNEL_DOUBLE) + ret_val = bacapp_snprintf_double(str, str_len, value->type.Double); +#endif + break; + case BACNET_APPLICATION_TAG_ENUMERATED: +#if defined(CHANNEL_ENUMERATED) + ret_val = bacapp_snprintf_enumerated( + str, str_len, OBJECT_COMMAND, PROP_ACTION, + value->type.Enumerated); +#endif + break; + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: +#if defined(CHANNEL_LIGHTING_COMMAND) + ret_val = lighting_command_to_ascii( + &value->type.Lighting_Command, str, str_len); +#endif + break; + case BACNET_APPLICATION_TAG_COLOR_COMMAND: +#if defined(CHANNEL_COLOR_COMMAND) + ret_val = bacapp_snprintf_color_command( + str, str_len, &value->type.Color_Command); +#endif + break; + case BACNET_APPLICATION_TAG_XY_COLOR: +#if defined(CHANNEL_XY_COLOR) + ret_val = xy_color_to_ascii(&value->type.XY_Color, str, str_len); +#endif + break; + default: + break; + } + + return ret_val; +} +#endif + +#if defined(BACAPP_CHANNEL) +/** + * @brief For a given application value, copy to the channel value + * @param cvalue - BACNET_CHANNEL_VALUE value + * @param value - BACNET_APPLICATION_DATA_VALUE value + * @return true if values are able to be copied + */ +bool bacapp_channel_value_copy( + BACNET_CHANNEL_VALUE *cvalue, const BACNET_APPLICATION_DATA_VALUE *value) +{ + bool status = false; + + if (!value || !cvalue) { + return false; + } + switch (value->tag) { +#if defined(BACAPP_NULL) + case BACNET_APPLICATION_TAG_NULL: + cvalue->tag = value->tag; + status = true; + break; +#endif +#if defined(BACAPP_BOOLEAN) && defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + cvalue->tag = value->tag; + cvalue->type.Boolean = value->type.Boolean; + status = true; + break; +#endif +#if defined(BACAPP_UNSIGNED) && defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + cvalue->tag = value->tag; + cvalue->type.Unsigned_Int = value->type.Unsigned_Int; + status = true; + break; +#endif +#if defined(BACAPP_SIGNED) && defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + cvalue->tag = value->tag; + cvalue->type.Signed_Int = value->type.Signed_Int; + status = true; + break; +#endif +#if defined(BACAPP_REAL) && defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + cvalue->tag = value->tag; + cvalue->type.Real = value->type.Real; + status = true; + break; +#endif +#if defined(BACAPP_DOUBLE) && defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + cvalue->tag = value->tag; + cvalue->type.Double = value->type.Double; + status = true; + break; +#endif +#if defined(BACAPP_OCTET_STRING) && defined(CHANNEL_OCTET_STRING) + case BACNET_APPLICATION_TAG_OCTET_STRING: + cvalue->tag = value->tag; + octetstring_copy( + &cvalue->type.Octet_String, &value->type.Octet_String); + status = true; + break; +#endif +#if defined(BACAPP_CHARACTER_STRING) && defined(CHANNEL_CHARACTER_STRING) + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + cvalue->tag = value->tag; + characterstring_copy( + &cvalue->type.Character_String, &value->type.Character_String); + status = true; + break; +#endif +#if defined(BACAPP_BIT_STRING) && defined(CHANNEL_BIT_STRING) + case BACNET_APPLICATION_TAG_BIT_STRING: + cvalue->tag = value->tag; + bitstring_copy(&cvalue->type.Bit_String, &value->type.Bit_String); + status = true; + break; +#endif +#if defined(BACAPP_ENUMERATED) && defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + cvalue->tag = value->tag; + cvalue->type.Enumerated = value->type.Enumerated; + status = true; + break; +#endif +#if defined(BACAPP_DATE) && defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + cvalue->tag = value->tag; + datetime_date_copy(&cvalue->type.Date, &value->type.Date); + apdu_len = encode_application_date(apdu, &value->type.Date); + status = true; + break; +#endif +#if defined(BACAPP_TIME) && defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + cvalue->tag = value->tag; + datetime_time_copy(&cvalue->type.Time, &value->type.Time); + break; +#endif +#if defined(BACAPP_OBJECT_ID) && defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + cvalue->tag = value->tag; + cvalue->type.Object_Id.type = value->type.Object_Id.type; + cvalue->type.Object_Id.instance = value->type.Object_Id.instance; + status = true; + break; +#endif +#if defined(BACAPP_LIGHTING_COMMAND) && defined(CHANNEL_LIGHTING_COMMAND) + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: + cvalue->tag = value->tag; + lighting_command_copy( + &cvalue->type.Lighting_Command, &value->type.Lighting_Command); + status = true; + break; +#endif +#if defined(BACAPP_COLOR_COMMAND) && defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + cvalue->tag = value->tag; + color_command_copy( + &cvalue->type.Color_Command, &value->type.Color_Command); + status = true; + break; +#endif +#if defined(BACAPP_COLOR_COMMAND) && defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + cvalue->tag = value->tag; + xy_color_copy(&cvalue->type.XY_Color, &value->type.XY_Color); + status = true; + break; +#endif + default: + break; + } + + return status; +} +#endif + #if defined(BACAPP_WEEKLY_SCHEDULE) /** * @brief Print a weekly schedule value to a string for EPICS @@ -3235,16 +3502,8 @@ int bacapp_snprintf_value( #if defined(BACAPP_COLOR_COMMAND) case BACNET_APPLICATION_TAG_COLOR_COMMAND: /* BACnetColorCommand */ - slen = bacapp_snprintf(str, str_len, "("); - ret_val += bacapp_snprintf_shift(slen, &str, &str_len); - slen = bacapp_snprintf( - str, str_len, "%s", - bactext_color_operation_name( - value->type.Color_Command.operation)); - ret_val += bacapp_snprintf_shift(slen, &str, &str_len); - /* FIXME: add the Lighting Command optional values */ - slen = bacapp_snprintf(str, str_len, ")"); - ret_val += slen; + ret_val = bacapp_snprintf_color_command( + str, str_len, &value->type.Color_Command); break; #endif #if defined(BACAPP_WEEKLY_SCHEDULE) @@ -3350,6 +3609,12 @@ int bacapp_snprintf_value( ret_val = bacapp_snprintf_access_rule( str, str_len, &value->type.Access_Rule); break; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + case BACNET_APPLICATION_TAG_CHANNEL_VALUE: + ret_val = bacapp_snprintf_channel_value( + str, str_len, &value->type.Channel_Value); + break; #endif case BACNET_APPLICATION_TAG_EMPTYLIST: ret_val = bacapp_snprintf(str, str_len, "{}"); @@ -3722,6 +3987,117 @@ bacnet_shed_level_from_ascii(BACNET_SHED_LEVEL *value, const char *argv) } #endif +#if defined(BACAPP_DEVICE_OBJECT_PROPERTY_REFERENCE) +/** + * @brief Parse a string into a BACnetDeviceObjectPropertyReference value + * @param value [out] The BACnetObjectPropertyReference value + * @param argv [in] The string to parse + * @return true on success, else false + */ +static bool device_object_property_reference_from_ascii( + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *value, const char *argv) +{ + bool status = false; + unsigned long object_type, object_instance; + unsigned long property_id; + long array_index; + unsigned long device_type, device_instance; + int count; + + if (!value || !argv) { + return false; + } + /* analog-output:4194303,present-value,-1,device:4194303 */ + count = sscanf( + argv, "%4lu:%7lu,%lu,%ld,%4lu:%7lu", &object_type, &object_instance, + &property_id, &array_index, &device_type, &device_instance); + if (count == 6) { + value->objectIdentifier.type = object_type; + value->objectIdentifier.instance = object_instance; + value->propertyIdentifier = property_id; + if (array_index < 0) { + value->arrayIndex = BACNET_ARRAY_ALL; + } else { + value->arrayIndex = array_index; + } + value->arrayIndex = array_index; + value->deviceIdentifier.type = device_type; + value->deviceIdentifier.instance = device_instance; + status = true; + } + + return status; +} +#endif +#if defined(BACAPP_DEVICE_OBJECT_REFERENCE) +/** + * @brief Parse a string into a BACnetDeviceObjectReference value + * @param value [out] The BACnetObjectPropertyReference value + * @param argv [in] The string to parse + * @return true on success, else false + */ +static bool device_object_reference_from_ascii( + BACNET_DEVICE_OBJECT_REFERENCE *value, const char *argv) +{ + bool status = false; + unsigned long object_type, object_instance; + unsigned long device_type, device_instance; + int count; + + if (!value || !argv) { + return false; + } + /* analog-output:4194303,device:4194303 */ + count = sscanf( + argv, "%4lu:%7lu,%4lu:%7lu", &object_type, &object_instance, + &device_type, &device_instance); + if (count == 4) { + value->objectIdentifier.type = object_type; + value->objectIdentifier.instance = object_instance; + value->deviceIdentifier.type = device_type; + value->deviceIdentifier.instance = device_instance; + status = true; + } + + return status; +} +#endif + +#if defined(BACAPP_OBJECT_PROPERTY_REFERENCE) +/** + * @brief Parse a string into a BACnetObjectPropertyReference value + * @param value [out] The BACnetObjectPropertyReference value + * @param argv [in] The string to parse + * @return true on success, else false + */ +static bool object_property_reference_from_ascii( + BACNET_OBJECT_PROPERTY_REFERENCE *value, const char *argv) +{ + bool status = false; + unsigned long object_type, object_instance; + unsigned long property_id; + long array_index; + int count; + + if (!value || !argv) { + return false; + } + /* analog-output:4194303,present-value,-1,device:4194303 */ + count = sscanf( + argv, "%4lu:%7lu,%lu,%ld", &object_type, &object_instance, &property_id, + &array_index); + if (count == 4) { + value->object_identifier.type = object_type; + value->object_identifier.instance = object_instance; + value->property_identifier = property_id; + value->property_array_index = array_index; + status = true; + } + + return status; +} +#endif + /* used to load the app data struct with the proper data converted from a command line argument. "argv" is not const to allow using strtok internally. It MAY be modified. */ @@ -3930,6 +4306,24 @@ bool bacapp_parse_application_data( host_n_port_from_ascii(&value->type.Host_Address, argv); break; #endif +#if defined(BACAPP_DEVICE_OBJECT_PROPERTY_REFERENCE) + case BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE: + status = device_object_property_reference_from_ascii( + &value->type.Device_Object_Property_Reference, argv); + break; +#endif +#if defined(BACAPP_DEVICE_OBJECT_REFERENCE) + case BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE: + status = device_object_reference_from_ascii( + &value->type.Device_Object_Reference, argv); + break; +#endif +#if defined(BACAPP_OBJECT_PROPERTY_REFERENCE) + case BACNET_APPLICATION_TAG_OBJECT_PROPERTY_REFERENCE: + status = object_property_reference_from_ascii( + &value->type.Object_Property_Reference, argv); + break; +#endif #if defined(BACAPP_DESTINATION) case BACNET_APPLICATION_TAG_DESTINATION: status = bacnet_destination_from_ascii( @@ -3966,9 +4360,14 @@ bool bacapp_parse_application_data( #endif #if defined(BACAPP_ACCESS_RULE) case BACNET_APPLICATION_TAG_ACCESS_RULE: - /* BACnetAccessRule - not implemented */ bacnet_access_rule_from_ascii(&value->type.Access_Rule, argv); break; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + case BACNET_APPLICATION_TAG_CHANNEL_VALUE: + bacnet_channel_value_from_ascii( + &value->type.Channel_Value, argv); + break; #endif default: break; @@ -4527,6 +4926,13 @@ bool bacapp_same_value( status = bacnet_access_rule_same( &value->type.Access_Rule, &test_value->type.Access_Rule); break; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + case BACNET_APPLICATION_TAG_CHANNEL_VALUE: + status = bacnet_channel_value_same( + &value->type.Channel_Value, + &test_value->type.Channel_Value); + break; #endif case BACNET_APPLICATION_TAG_EMPTYLIST: status = true; diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h index 38dee146..6be770b1 100644 --- a/src/bacnet/bacapp.h +++ b/src/bacnet/bacapp.h @@ -28,6 +28,7 @@ #include "bacnet/weeklyschedule.h" #include "bacnet/calendar_entry.h" #include "bacnet/special_event.h" +#include "bacnet/channel_value.h" #ifndef BACAPP_PRINT_ENABLED #if PRINT_ENABLED @@ -163,6 +164,9 @@ typedef struct BACnet_Application_Data_Value { #endif #if defined(BACAPP_ACCESS_RULE) BACNET_ACCESS_RULE Access_Rule; +#endif +#if defined(BACAPP_CHANNEL_VALUE) + BACNET_CHANNEL_VALUE Channel_Value; #endif } type; /* simple linked list if needed */ @@ -354,6 +358,10 @@ int bacapp_snprintf_value( size_t str_len, const BACNET_OBJECT_PROPERTY_VALUE *object_value); +BACNET_STACK_EXPORT +bool bacapp_channel_value_copy( + BACNET_CHANNEL_VALUE *cvalue, const BACNET_APPLICATION_DATA_VALUE *value); + BACNET_STACK_EXPORT bool bacapp_parse_application_data( BACNET_APPLICATION_TAG tag_number, diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 133004ae..55ecc4ee 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1610,7 +1610,9 @@ typedef enum { /* BACnetShedLevel */ BACNET_APPLICATION_TAG_SHED_LEVEL, /* BACnetAccessRule */ - BACNET_APPLICATION_TAG_ACCESS_RULE + BACNET_APPLICATION_TAG_ACCESS_RULE, + /* BACnetChannelValue */ + BACNET_APPLICATION_TAG_CHANNEL_VALUE } BACNET_APPLICATION_TAG; /* note: these are not the real values, */ diff --git a/src/bacnet/basic/object/channel.c b/src/bacnet/basic/object/channel.c index 6d3104f8..d21bbc12 100644 --- a/src/bacnet/basic/object/channel.c +++ b/src/bacnet/basic/object/channel.c @@ -19,6 +19,9 @@ #include "bacnet/wp.h" #include "bacnet/basic/services.h" #include "bacnet/proplist.h" +#include "bacnet/property.h" +#include "bacnet/bactext.h" +#include "bacnet/basic/sys/debug.h" #include "bacnet/basic/sys/keylist.h" #if defined(CHANNEL_LIGHTING_COMMAND) || defined(CHANNEL_COLOR_COMMAND) #include "bacnet/lighting.h" @@ -40,7 +43,7 @@ struct object_data { unsigned Last_Priority; BACNET_WRITE_STATUS Write_Status; BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE Members[CHANNEL_MEMBERS_MAX]; - uint16_t Number; + uint16_t Channel_Number; uint32_t Control_Groups[CONTROL_GROUPS_MAX]; const char *Object_Name; const char *Description; @@ -99,6 +102,16 @@ void Channel_Property_Lists( return; } +/** + * @brief Get a Channel object + * @param object_instance [in] BACnet object instance number + * @return pointer to the object data structure, or NULL if not present + */ +struct object_data *Object_Data(uint32_t object_instance) +{ + return Keylist_Data(Object_List, object_instance); +} + /** * Determines if a given Channel instance is valid * @@ -108,9 +121,9 @@ void Channel_Property_Lists( */ bool Channel_Valid_Instance(uint32_t object_instance) { - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { return true; } @@ -170,7 +183,7 @@ BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance) BACNET_CHANNEL_VALUE *cvalue = NULL; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { cvalue = &pObject->Present_Value; } @@ -188,9 +201,9 @@ BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance) unsigned Channel_Last_Priority(uint32_t object_instance) { unsigned priority = 0; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { priority = pObject->Last_Priority; } @@ -208,9 +221,9 @@ unsigned Channel_Last_Priority(uint32_t object_instance) BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance) { BACNET_WRITE_STATUS write_status = BACNET_WRITE_STATUS_IDLE; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { write_status = pObject->Write_Status; } @@ -219,20 +232,19 @@ BACNET_WRITE_STATUS Channel_Write_Status(uint32_t object_instance) } /** - * For a given object instance-number, determines the Number - * + * @brief For a given object instance-number, determines the channel-number + * property value * @param object_instance - object-instance number of the object - * - * @return Channel Number value + * @return channel-number property value */ uint16_t Channel_Number(uint32_t object_instance) { uint16_t value = 0; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { - value = pObject->Number; + value = pObject->Channel_Number; } return value; @@ -252,9 +264,9 @@ bool Channel_Number_Set(uint32_t object_instance, uint16_t value) bool status = false; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { - pObject->Number = value; + pObject->Channel_Number = value; status = true; } @@ -289,20 +301,26 @@ static int Channel_Reference_List_Member_Element_Encode( } /** - * For a given object instance-number, determines the member count + * For a given object instance-number, determines if the member is non-empty * - * @param object_instance - object-instance number of the object + * 12.53.11.1 Empty References + * Elements of the List_Of_Object_Property_References array containing + * object or device instance numbers equal to 4194303 are considered to + * be 'empty' or 'uninitialized'. * + * @param pMember - object property reference element * @return member count */ -static bool Channel_Reference_List_Member_Valid( +static bool Channel_Reference_List_Member_Empty( const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember) { bool status = false; - if ((pMember) && - (pMember->objectIdentifier.instance != BACNET_MAX_INSTANCE) && - (pMember->deviceIdentifier.instance != BACNET_MAX_INSTANCE)) { + if (!pMember) { + return false; + } + if ((pMember->objectIdentifier.instance == BACNET_MAX_INSTANCE) || + (pMember->deviceIdentifier.instance == BACNET_MAX_INSTANCE)) { status = true; } @@ -336,7 +354,7 @@ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Channel_Reference_List_Member_Element( BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject && (array_index > 0)) { array_index--; if (array_index < CHANNEL_MEMBERS_MAX) { @@ -350,38 +368,70 @@ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *Channel_Reference_List_Member_Element( /** * For a given object instance-number, returns the member element * - * @param object_instance - object-instance number of the object - * @param array_index - 1-based array index - * - * @return pointer to member element or NULL if not found + * @param pObject - object in which to set the value + * @param index - 0-based array index + * @param pMember - pointer to member value + * @return true if set, false if not set */ -bool Channel_Reference_List_Member_Element_Set( - uint32_t object_instance, - unsigned array_index, - const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc) +static bool List_Of_Object_Property_References_Set( + struct object_data *pObject, + unsigned index, + const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember) { - BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; bool status = false; - struct object_data *pObject; - - pObject = Keylist_Data(Object_List, object_instance); - if (pObject && (array_index > 0)) { - array_index--; - if (array_index < CHANNEL_MEMBERS_MAX) { - pMember = &pObject->Members[array_index]; + if (pObject && (index < CHANNEL_MEMBERS_MAX)) { + if (pMember) { memcpy( - pMember, pMemberSrc, + &pObject->Members[index], pMember, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); - status = true; + } else { + pObject->Members[index].objectIdentifier.instance = + BACNET_MAX_INSTANCE; + pObject->Members[index].deviceIdentifier.instance = + BACNET_MAX_INSTANCE; + pObject->Members[index].objectIdentifier.type = + OBJECT_LIGHTING_OUTPUT; + pObject->Members[index].objectIdentifier.instance = + BACNET_MAX_INSTANCE; + pObject->Members[index].propertyIdentifier = PROP_PRESENT_VALUE; + pObject->Members[index].arrayIndex = BACNET_ARRAY_ALL; + pObject->Members[index].deviceIdentifier.type = OBJECT_DEVICE; + pObject->Members[index].deviceIdentifier.instance = + BACNET_MAX_INSTANCE; } + status = true; } return status; } /** - * For a given object instance-number, adds a member element - * + * @brief For a given object instance-number, set the member element value + * @param object_instance - object-instance number of the object + * @param array_index - 1-based array index + * @return pointer to member element or NULL if not found + */ +bool Channel_Reference_List_Member_Element_Set( + uint32_t object_instance, + unsigned array_index, + const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember) +{ + bool status = false; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject && (array_index > 0)) { + array_index--; + status = List_Of_Object_Property_References_Set( + pObject, array_index, pMember); + } + + return status; +} + +/** + * @brief For a given object instance-number, adds a member element to the + * first empty slot * @param object_instance - object-instance number of the object * @param pMemberSrc - pointer to a object property reference element * @@ -390,22 +440,22 @@ bool Channel_Reference_List_Member_Element_Set( */ unsigned Channel_Reference_List_Member_Element_Add( uint32_t object_instance, - const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMemberSrc) + const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pNewMember) { BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL; unsigned array_index = 0; unsigned m = 0; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { pMember = &pObject->Members[m]; - if (!Channel_Reference_List_Member_Valid(pMember)) { + if (Channel_Reference_List_Member_Empty(pMember)) { /* first empty slot */ array_index = 1 + m; memcpy( - pMember, pMemberSrc, + pMember, pNewMember, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE)); break; } @@ -416,20 +466,19 @@ unsigned Channel_Reference_List_Member_Element_Add( } /** - * For a given object instance-number, determines the Number - * + * @brief For a given object instance-number and index, gets the + * control-groups value * @param object_instance - object-instance number of the object * @param array_index - 1-based array index - * - * @return group number in the array, or 0 if invalid + * @return control-groups value for the given index */ uint16_t Channel_Control_Groups_Element(uint32_t object_instance, int32_t array_index) { uint16_t value = 0; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { if ((array_index > 0) && (array_index <= CONTROL_GROUPS_MAX)) { array_index--; @@ -440,6 +489,29 @@ Channel_Control_Groups_Element(uint32_t object_instance, int32_t array_index) return value; } +/** + * @brief Write the object property member value + * @param array_index - 1-based array index + * @param value - control group value 0..65535 + * + * @return true if parameters are value and control group is set + */ +static bool Control_Groups_Element_Set( + struct object_data *pObject, int32_t array_index, uint16_t value) +{ + bool status = false; + + if (pObject) { + if ((array_index > 0) && (array_index <= CONTROL_GROUPS_MAX)) { + array_index--; + pObject->Control_Groups[array_index] = value; + status = true; + } + } + + return status; +} + /** * For a given object instance-number, determines the Number * @@ -455,13 +527,9 @@ bool Channel_Control_Groups_Element_Set( bool status = false; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { - if ((array_index > 0) && (array_index <= CONTROL_GROUPS_MAX)) { - array_index--; - pObject->Control_Groups[array_index] = value; - status = true; - } + status = Control_Groups_Element_Set(pObject, array_index, value); } return status; @@ -482,9 +550,9 @@ static int Channel_Control_Groups_Element_Encode( { int apdu_len = BACNET_STATUS_ERROR; uint16_t value = 1; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject && (array_index < CONTROL_GROUPS_MAX)) { value = Channel_Control_Groups_Element(object_instance, array_index + 1); @@ -494,613 +562,17 @@ static int Channel_Control_Groups_Element_Encode( return apdu_len; } -/** - * For a given application value, copy to the channel value - * - * @param cvalue - BACNET_CHANNEL_VALUE value - * @param value - BACNET_APPLICATION_DATA_VALUE value - * - * @return true if values are able to be copied - */ -bool Channel_Value_Copy( - BACNET_CHANNEL_VALUE *cvalue, const BACNET_APPLICATION_DATA_VALUE *value) -{ - bool status = false; - - if (!value || !cvalue) { - return false; - } - switch (value->tag) { -#if defined(BACAPP_NULL) - case BACNET_APPLICATION_TAG_NULL: - cvalue->tag = value->tag; - status = true; - break; -#endif -#if defined(BACAPP_BOOLEAN) && defined(CHANNEL_BOOLEAN) - case BACNET_APPLICATION_TAG_BOOLEAN: - cvalue->tag = value->tag; - cvalue->type.Boolean = value->type.Boolean; - status = true; - break; -#endif -#if defined(BACAPP_UNSIGNED) && defined(CHANNEL_UNSIGNED) - case BACNET_APPLICATION_TAG_UNSIGNED_INT: - cvalue->tag = value->tag; - cvalue->type.Unsigned_Int = value->type.Unsigned_Int; - status = true; - break; -#endif -#if defined(BACAPP_SIGNED) && defined(CHANNEL_SIGNED) - case BACNET_APPLICATION_TAG_SIGNED_INT: - cvalue->tag = value->tag; - cvalue->type.Signed_Int = value->type.Signed_Int; - status = true; - break; -#endif -#if defined(BACAPP_REAL) && defined(CHANNEL_REAL) - case BACNET_APPLICATION_TAG_REAL: - cvalue->tag = value->tag; - cvalue->type.Real = value->type.Real; - status = true; - break; -#endif -#if defined(BACAPP_DOUBLE) && defined(CHANNEL_DOUBLE) - case BACNET_APPLICATION_TAG_DOUBLE: - cvalue->tag = value->tag; - cvalue->type.Double = value->type.Double; - status = true; - break; -#endif -#if defined(BACAPP_OCTET_STRING) && defined(CHANNEL_OCTET_STRING) - case BACNET_APPLICATION_TAG_OCTET_STRING: - cvalue->tag = value->tag; - octetstring_copy( - &cvalue->type.Octet_String, &value->type.Octet_String); - status = true; - break; -#endif -#if defined(BACAPP_CHARACTER_STRING) && defined(CHANNEL_CHARACTER_STRING) - case BACNET_APPLICATION_TAG_CHARACTER_STRING: - cvalue->tag = value->tag; - characterstring_copy( - &cvalue->type.Character_String, &value->type.Character_String); - status = true; - break; -#endif -#if defined(BACAPP_BIT_STRING) && defined(CHANNEL_BIT_STRING) - case BACNET_APPLICATION_TAG_BIT_STRING: - cvalue->tag = value->tag; - bitstring_copy(&cvalue->type.Bit_String, &value->type.Bit_String); - status = true; - break; -#endif -#if defined(BACAPP_ENUMERATED) && defined(CHANNEL_ENUMERATED) - case BACNET_APPLICATION_TAG_ENUMERATED: - cvalue->tag = value->tag; - cvalue->type.Enumerated = value->type.Enumerated; - status = true; - break; -#endif -#if defined(BACAPP_DATE) && defined(CHANNEL_DATE) - case BACNET_APPLICATION_TAG_DATE: - cvalue->tag = value->tag; - datetime_date_copy(&cvalue->type.Date, &value->type.Date); - apdu_len = encode_application_date(apdu, &value->type.Date); - status = true; - break; -#endif -#if defined(BACAPP_TIME) && defined(CHANNEL_TIME) - case BACNET_APPLICATION_TAG_TIME: - cvalue->tag = value->tag; - datetime_time_copy(&cvalue->type.Time, &value->type.Time); - break; -#endif -#if defined(BACAPP_OBJECT_ID) && defined(CHANNEL_OBJECT_ID) - case BACNET_APPLICATION_TAG_OBJECT_ID: - cvalue->tag = value->tag; - cvalue->type.Object_Id.type = value->type.Object_Id.type; - cvalue->type.Object_Id.instance = value->type.Object_Id.instance; - status = true; - break; -#endif -#if defined(BACAPP_TYPES_EXTRA) && defined(CHANNEL_LIGHTING_COMMAND) - case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: - cvalue->tag = value->tag; - lighting_command_copy( - &cvalue->type.Lighting_Command, &value->type.Lighting_Command); - status = true; - break; -#endif -#if defined(BACAPP_TYPES_EXTRA) && defined(CHANNEL_COLOR_COMMAND) - case BACNET_APPLICATION_TAG_COLOR_COMMAND: - cvalue->tag = value->tag; - color_command_copy( - &cvalue->type.Color_Command, &value->type.Color_Command); - status = true; - break; -#endif -#if defined(BACAPP_TYPES_EXTRA) && defined(CHANNEL_XY_COLOR) - case BACNET_APPLICATION_TAG_XY_COLOR: - cvalue->tag = value->tag; - xy_color_copy(&cvalue->type.XY_Color, &value->type.XY_Color); - status = true; - break; -#endif - default: - break; - } - - return status; -} - -/** - * For a given application value, copy to the channel value - * - * @param apdu - APDU buffer for storing the encoded data, or NULL for length - * @param apdu_max - size of APDU buffer available for storing data - * @param value - BACNET_CHANNEL_VALUE value - * - * @return number of bytes in the APDU, or BACNET_STATUS_ERROR - */ -int Channel_Value_Encode( - uint8_t *apdu, int apdu_max, const BACNET_CHANNEL_VALUE *value) -{ - int apdu_len = BACNET_STATUS_ERROR; - - (void)apdu_max; - if (!value) { - return BACNET_STATUS_ERROR; - } - switch (value->tag) { - case BACNET_APPLICATION_TAG_NULL: - apdu_len = encode_application_null(apdu); - break; -#if defined(CHANNEL_BOOLEAN) - case BACNET_APPLICATION_TAG_BOOLEAN: - apdu_len = encode_application_boolean(apdu, value->type.Boolean); - break; -#endif -#if defined(CHANNEL_UNSIGNED) - case BACNET_APPLICATION_TAG_UNSIGNED_INT: - apdu_len = - encode_application_unsigned(apdu, value->type.Unsigned_Int); - break; -#endif -#if defined(CHANNEL_SIGNED) - case BACNET_APPLICATION_TAG_SIGNED_INT: - apdu_len = encode_application_signed(apdu, value->type.Signed_Int); - break; -#endif -#if defined(CHANNEL_REAL) - case BACNET_APPLICATION_TAG_REAL: - apdu_len = encode_application_real(apdu, value->type.Real); - break; -#endif -#if defined(CHANNEL_DOUBLE) - case BACNET_APPLICATION_TAG_DOUBLE: - apdu_len = encode_application_double(apdu, value->type.Double); - break; -#endif -#if defined(CHANNEL_OCTET_STRING) - case BACNET_APPLICATION_TAG_OCTET_STRING: - apdu_len = encode_application_octet_string( - apdu, &value->type.Octet_String); - break; -#endif -#if defined(CHANNEL_CHARACTER_STRING) - case BACNET_APPLICATION_TAG_CHARACTER_STRING: - apdu_len = encode_application_character_string( - apdu, &value->type.Character_String); - break; -#endif -#if defined(CHANNEL_BIT_STRING) - case BACNET_APPLICATION_TAG_BIT_STRING: - apdu_len = - encode_application_bitstring(apdu, &value->type.Bit_String); - break; -#endif -#if defined(CHANNEL_ENUMERATED) - case BACNET_APPLICATION_TAG_ENUMERATED: - apdu_len = - encode_application_enumerated(apdu, value->type.Enumerated); - break; -#endif -#if defined(CHANNEL_DATE) - case BACNET_APPLICATION_TAG_DATE: - apdu_len = encode_application_date(apdu, &value->type.Date); - break; -#endif -#if defined(CHANNEL_TIME) - case BACNET_APPLICATION_TAG_TIME: - apdu_len = encode_application_time(apdu, &value->type.Time); - break; -#endif -#if defined(CHANNEL_OBJECT_ID) - case BACNET_APPLICATION_TAG_OBJECT_ID: - apdu_len = encode_application_object_id( - apdu, (int)value->type.Object_Id.type, - value->type.Object_Id.instance); - break; -#endif -#if defined(CHANNEL_LIGHTING_COMMAND) - case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: - apdu_len = - lighting_command_encode(apdu, &value->type.Lighting_Command); - break; -#endif -#if defined(CHANNEL_COLOR_COMMAND) - case BACNET_APPLICATION_TAG_COLOR_COMMAND: - apdu_len = color_command_encode(apdu, &value->type.Color_Command); - break; -#endif -#if defined(CHANNEL_XY_COLOR) - case BACNET_APPLICATION_TAG_XY_COLOR: - apdu_len = xy_color_encode(apdu, &value->type.XY_Color); - break; -#endif - default: - break; - } - - return apdu_len; -} - -/** - * For a given application value, coerce the encoding, if necessary - * - * @param apdu - buffer to hold the encoding, or NULL for length - * @param value - BACNET_APPLICATION_DATA_VALUE value - * @param tag - application tag to be coerced, if possible - * - * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. - */ -static int Coerce_Data_Encode( - uint8_t *apdu, - const BACNET_APPLICATION_DATA_VALUE *value, - BACNET_APPLICATION_TAG tag) -{ - int apdu_len = 0; /* total length of the apdu, return value */ - float float_value = 0.0; - double double_value = 0.0; - uint32_t unsigned_value = 0; - int32_t signed_value = 0; - bool boolean_value = false; - - if (value) { - switch (value->tag) { -#if defined(BACAPP_NULL) - case BACNET_APPLICATION_TAG_NULL: - if ((tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) || - (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND)) { - apdu_len = BACNET_STATUS_ERROR; - } else { - /* no coercion */ - if (apdu) { - *apdu = value->tag; - } - apdu_len++; - } - break; -#endif -#if defined(BACAPP_BOOLEAN) - case BACNET_APPLICATION_TAG_BOOLEAN: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - apdu_len = - encode_application_boolean(apdu, value->type.Boolean); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - if (value->type.Boolean) { - unsigned_value = 1; - } - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - if (value->type.Boolean) { - signed_value = 1; - } - apdu_len = encode_application_signed(apdu, signed_value); - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - if (value->type.Boolean) { - float_value = 1; - } - apdu_len = encode_application_real(apdu, float_value); - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - if (value->type.Boolean) { - double_value = 1; - } - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - if (value->type.Boolean) { - unsigned_value = 1; - } - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_UNSIGNED) - case BACNET_APPLICATION_TAG_UNSIGNED_INT: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - if (value->type.Unsigned_Int) { - boolean_value = true; - } - apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - unsigned_value = value->type.Unsigned_Int; - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - if (value->type.Unsigned_Int <= 2147483647) { - signed_value = value->type.Unsigned_Int; - apdu_len = - encode_application_signed(apdu, signed_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - if (value->type.Unsigned_Int <= 9999999) { - float_value = (float)value->type.Unsigned_Int; - apdu_len = encode_application_real(apdu, float_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = (double)value->type.Unsigned_Int; - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - unsigned_value = value->type.Unsigned_Int; - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_SIGNED) - case BACNET_APPLICATION_TAG_SIGNED_INT: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - if (value->type.Signed_Int) { - boolean_value = true; - } - apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - if ((value->type.Signed_Int >= 0) && - (value->type.Signed_Int <= 2147483647)) { - unsigned_value = value->type.Signed_Int; - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - signed_value = value->type.Signed_Int; - apdu_len = encode_application_signed(apdu, signed_value); - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - if (value->type.Signed_Int <= 9999999) { - float_value = (float)value->type.Signed_Int; - apdu_len = encode_application_real(apdu, float_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = (double)value->type.Signed_Int; - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - unsigned_value = value->type.Signed_Int; - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_REAL) - case BACNET_APPLICATION_TAG_REAL: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - if (islessgreater(value->type.Real, 0.0F)) { - boolean_value = true; - } - apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - if ((value->type.Real >= 0.0F) && - (value->type.Real <= 2147483000.0F)) { - unsigned_value = (uint32_t)value->type.Real; - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - if ((value->type.Real >= -2147483000.0F) && - (value->type.Real <= 214783000.0F)) { - signed_value = (int32_t)value->type.Real; - apdu_len = - encode_application_signed(apdu, signed_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - float_value = value->type.Real; - apdu_len = encode_application_real(apdu, float_value); - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = value->type.Real; - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - if ((value->type.Real >= 0.0F) && - (value->type.Real <= 2147483000.0F)) { - unsigned_value = (uint32_t)value->type.Real; - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_DOUBLE) - case BACNET_APPLICATION_TAG_DOUBLE: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - if (islessgreater(value->type.Double, 0.0)) { - boolean_value = true; - } - apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - if ((value->type.Double >= 0.0) && - (value->type.Double <= 2147483000.0)) { - unsigned_value = (uint32_t)value->type.Double; - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - if ((value->type.Double >= -2147483000.0) && - (value->type.Double <= 214783000.0)) { - signed_value = (int32_t)value->type.Double; - apdu_len = - encode_application_signed(apdu, signed_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - if ((value->type.Double >= 3.4E-38) && - (value->type.Double <= 3.4E+38)) { - float_value = (float)value->type.Double; - apdu_len = encode_application_real(apdu, float_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = value->type.Double; - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - if ((value->type.Double >= 0.0) && - (value->type.Double <= 2147483000.0)) { - unsigned_value = (uint32_t)value->type.Double; - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_ENUMERATED) - case BACNET_APPLICATION_TAG_ENUMERATED: - if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { - if (value->type.Enumerated) { - boolean_value = true; - } - apdu_len = encode_application_boolean(apdu, boolean_value); - } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { - unsigned_value = value->type.Enumerated; - apdu_len = - encode_application_unsigned(apdu, unsigned_value); - } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { - if (value->type.Enumerated <= 2147483647) { - signed_value = value->type.Enumerated; - apdu_len = - encode_application_signed(apdu, signed_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_REAL) { - if (value->type.Enumerated <= 9999999) { - float_value = (float)value->type.Enumerated; - apdu_len = encode_application_real(apdu, float_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { - double_value = (double)value->type.Enumerated; - apdu_len = encode_application_double(apdu, double_value); - } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { - unsigned_value = value->type.Enumerated; - apdu_len = - encode_application_enumerated(apdu, unsigned_value); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif -#if defined(BACAPP_TYPES_EXTRA) - case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: - if (tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) { - apdu_len = lighting_command_encode( - apdu, &value->type.Lighting_Command); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; - case BACNET_APPLICATION_TAG_COLOR_COMMAND: - if (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND) { - apdu_len = - color_command_encode(apdu, &value->type.Color_Command); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; - case BACNET_APPLICATION_TAG_XY_COLOR: - if (tag == BACNET_APPLICATION_TAG_XY_COLOR) { - apdu_len = xy_color_encode(apdu, &value->type.XY_Color); - } else { - apdu_len = BACNET_STATUS_ERROR; - } - break; -#endif - default: - apdu_len = BACNET_STATUS_ERROR; - break; - } - } - - return apdu_len; -} - -/** - * For a given application value, coerce the encoding, if necessary - * - * @param apdu - buffer to hold the encoding, or null for length - * @param value - BACNET_APPLICATION_DATA_VALUE value - * @param tag - application tag to be coerced, if possible - * - * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. - */ -int Channel_Coerce_Data_Encode( - uint8_t *apdu, - size_t apdu_size, - const BACNET_APPLICATION_DATA_VALUE *value, - BACNET_APPLICATION_TAG tag) -{ - int len; - - len = Coerce_Data_Encode(NULL, value, tag); - if ((len > 0) && (len <= apdu_size)) { - len = Coerce_Data_Encode(apdu, value, tag); - } else { - len = BACNET_STATUS_ERROR; - } - - return len; -} - /** * For a given object instance-number, sets the present-value at a given * priority 1..16. * * @param wp_data - all of the WriteProperty data structure + * @param value - BACnetChannelValue * * @return true if values are within range and present-value is sent. */ bool Channel_Write_Member_Value( - BACNET_WRITE_PROPERTY_DATA *wp_data, - const BACNET_APPLICATION_DATA_VALUE *value) + BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_CHANNEL_VALUE *value) { bool status = false; int apdu_len = 0; @@ -1111,7 +583,7 @@ bool Channel_Write_Member_Value( (wp_data->object_type == OBJECT_ANALOG_VALUE)) && (wp_data->object_property == PROP_PRESENT_VALUE) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_REAL); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1124,7 +596,7 @@ bool Channel_Write_Member_Value( (wp_data->object_type == OBJECT_BINARY_VALUE)) && (wp_data->object_property == PROP_PRESENT_VALUE) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_ENUMERATED); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1137,7 +609,7 @@ bool Channel_Write_Member_Value( (wp_data->object_type == OBJECT_MULTI_STATE_VALUE)) && (wp_data->object_property == PROP_PRESENT_VALUE) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_UNSIGNED_INT); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1147,7 +619,7 @@ bool Channel_Write_Member_Value( } else if (wp_data->object_type == OBJECT_LIGHTING_OUTPUT) { if ((wp_data->object_property == PROP_PRESENT_VALUE) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_REAL); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1157,7 +629,7 @@ bool Channel_Write_Member_Value( } else if ( (wp_data->object_property == PROP_LIGHTING_COMMAND) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_LIGHTING_COMMAND); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1168,7 +640,7 @@ bool Channel_Write_Member_Value( } else if (wp_data->object_type == OBJECT_COLOR) { if ((wp_data->object_property == PROP_PRESENT_VALUE) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_XY_COLOR); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1178,7 +650,7 @@ bool Channel_Write_Member_Value( } else if ( (wp_data->object_property == PROP_COLOR_COMMAND) && (wp_data->array_index == BACNET_ARRAY_ALL)) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_COLOR_COMMAND); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1187,7 +659,7 @@ bool Channel_Write_Member_Value( } } } else if (wp_data->object_type == OBJECT_COLOR_TEMPERATURE) { - apdu_len = Channel_Coerce_Data_Encode( + apdu_len = bacnet_channel_value_coerce_data_encode( wp_data->application_data, wp_data->application_data_len, value, BACNET_APPLICATION_TAG_UNSIGNED_INT); if (apdu_len != BACNET_STATUS_ERROR) { @@ -1212,7 +684,8 @@ bool Channel_Write_Member_Value( */ static bool Channel_Write_Members( struct object_data *pObject, - const BACNET_APPLICATION_DATA_VALUE *value, + uint32_t object_instance, + const BACNET_CHANNEL_VALUE *value, uint8_t priority) { BACNET_WRITE_PROPERTY_DATA wp_data = { 0 }; @@ -1222,6 +695,10 @@ static bool Channel_Write_Members( if (pObject && value) { pObject->Write_Status = BACNET_WRITE_STATUS_IN_PROGRESS; + debug_printf( + "channel[%lu].Channel_Write_Members\n", + (unsigned long)object_instance); + for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { pMember = &pObject->Members[m]; /* NOTE: our implementation is for internal objects only */ @@ -1236,16 +713,37 @@ static bool Channel_Write_Members( wp_data.object_instance = pMember->objectIdentifier.instance; wp_data.object_property = pMember->propertyIdentifier; wp_data.array_index = pMember->arrayIndex; + wp_data.error_class = ERROR_CLASS_PROPERTY; + wp_data.error_code = ERROR_CODE_SUCCESS; wp_data.priority = priority; wp_data.application_data_len = sizeof(wp_data.application_data); status = Channel_Write_Member_Value(&wp_data, value); if (status) { + debug_printf( + "channel[%lu].Channel_Write_Member[%u] coerced\n", + (unsigned long)object_instance, m); if (Write_Property_Internal_Callback) { status = Write_Property_Internal_Callback(&wp_data); + if (status) { + wp_data.error_code = ERROR_CODE_SUCCESS; + } + debug_printf( + "channel[%lu].Channel_Write_Member[%u] " + "%s\n", + (unsigned long)object_instance, m, + bactext_error_code_name(wp_data.error_code)); } } else { + debug_printf( + "channel[%lu].Channel_Write_Member[%u] " + "coercion failed!\n", + (unsigned long)object_instance, m); pObject->Write_Status = BACNET_WRITE_STATUS_FAILED; } + } else { + debug_printf( + "channel[%lu].Channel_Write_Member[%u] invalid!\n", + (unsigned long)object_instance, m); } } if (pObject->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) { @@ -1265,22 +763,59 @@ static bool Channel_Write_Members( * @return true if values are within range and present-value is sent. */ bool Channel_Present_Value_Set( - BACNET_WRITE_PROPERTY_DATA *wp_data, - const BACNET_APPLICATION_DATA_VALUE *value) + uint32_t object_instance, + uint8_t priority, + const BACNET_CHANNEL_VALUE *value) { bool status = false; struct object_data *pObject; - pObject = Keylist_Data(Object_List, wp_data->object_instance); + pObject = Object_Data(object_instance); + if (pObject) { + if ((priority > 0) && (priority <= BACNET_MAX_PRIORITY)) { + bacnet_channel_value_copy(&pObject->Present_Value, value); + status = Channel_Write_Members( + pObject, object_instance, value, priority); + if (status) { + pObject->Last_Priority = priority; + } + } + } + + return status; +} + +/** + * For a given object instance-number, sets the present-value at a given + * priority 1..16. + * + * @param wp_data - all of the WriteProperty data structure + * @param value - application value + * @return true if values are within range and present-value is sent. + */ +static bool Channel_Present_Value_Write( + BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_CHANNEL_VALUE *value) +{ + bool status = false; + struct object_data *pObject; + + pObject = Object_Data(wp_data->object_instance); if (pObject) { if ((wp_data->priority > 0) && (wp_data->priority <= BACNET_MAX_PRIORITY)) { if (wp_data->priority != 6 /* reserved */) { - status = Channel_Value_Copy(&pObject->Present_Value, value); - (void)status; - status = - Channel_Write_Members(pObject, value, wp_data->priority); - (void)status; + bacnet_channel_value_copy(&pObject->Present_Value, value); + status = Channel_Write_Members( + pObject, wp_data->object_instance, value, + wp_data->priority); + if (status) { + pObject->Last_Priority = wp_data->priority; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_SUCCESS; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } status = true; } else { /* Command priority 6 is reserved for use by Minimum On/Off @@ -1313,9 +848,9 @@ bool Channel_Object_Name( { bool status = false; char name_text[24] = "CHANNEL-4194303"; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { if (pObject->Object_Name) { status = @@ -1344,7 +879,7 @@ bool Channel_Name_Set(uint32_t object_instance, const char *new_name) bool status = false; /* return value */ struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { status = true; pObject->Object_Name = new_name; @@ -1363,7 +898,7 @@ const char *Channel_Name_ASCII(uint32_t object_instance) const char *name = NULL; struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { name = pObject->Object_Name; } @@ -1382,9 +917,9 @@ const char *Channel_Name_ASCII(uint32_t object_instance) bool Channel_Out_Of_Service(uint32_t object_instance) { bool value = false; - const struct object_data *pObject; + struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { value = pObject->Out_Of_Service; } @@ -1404,7 +939,7 @@ void Channel_Out_Of_Service_Set(uint32_t object_instance, bool value) { struct object_data *pObject; - pObject = Keylist_Data(Object_List, object_instance); + pObject = Object_Data(object_instance); if (pObject) { pObject->Out_Of_Service = value; } @@ -1431,6 +966,7 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) bool state = false; int apdu_size = 0; uint8_t *apdu = NULL; + bool is_array; if ((rpdata == NULL) || (rpdata->application_data == NULL) || (rpdata->application_data_len == 0)) { @@ -1452,7 +988,7 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; case PROP_PRESENT_VALUE: cvalue = Channel_Present_Value(rpdata->object_instance); - apdu_len = Channel_Value_Encode(apdu, MAX_APDU, cvalue); + apdu_len = bacnet_channel_value_encode(apdu, apdu_size, cvalue); if (apdu_len == BACNET_STATUS_ERROR) { apdu_len = encode_application_null(apdu); } @@ -1518,8 +1054,9 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; } /* only array properties can have array options */ - if ((apdu_len >= 0) && (rpdata->object_property != PROP_CONTROL_GROUPS) && - (rpdata->object_property != PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) && + is_array = property_list_bacnet_array_member( + rpdata->object_type, rpdata->object_property); + if ((apdu_len >= 0) && (!is_array) && (rpdata->array_index != BACNET_ARRAY_ALL)) { rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; @@ -1529,6 +1066,165 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) return apdu_len; } +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Channel_List_Of_Object_Property_References_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + int len = 0; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + len = bacapp_decode_known_property( + apdu, apdu_size, &value, OBJECT_CHANNEL, + PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES); + } + + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Channel_List_Of_Object_Property_References_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + int len = 0; + bool status; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (array_index == 0) { + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (array_index <= CHANNEL_MEMBERS_MAX) { + len = bacapp_decode_known_property( + application_data, application_data_len, &value, OBJECT_CHANNEL, + PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES); + if (len > 0) { + if (value.tag == + BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE) { + status = List_Of_Object_Property_References_Set( + pObject, array_index - 1, + &value.type.Device_Object_Property_Reference); + if (status) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_ABORT_OTHER; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + } + + return error_code; +} + +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Channel_Control_Groups_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + int len = 0; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + len = bacapp_decode_known_property( + apdu, apdu_size, &value, OBJECT_CHANNEL, PROP_CONTROL_GROUPS); + } + + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Channel_Control_Groups_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + uint16_t control_group; + int len = 0; + bool status; + struct object_data *pObject; + + pObject = Object_Data(object_instance); + if (pObject) { + if (array_index == 0) { + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (array_index <= CHANNEL_MEMBERS_MAX) { + len = bacapp_decode_known_property( + application_data, application_data_len, &value, OBJECT_CHANNEL, + PROP_CONTROL_GROUPS); + if (len > 0) { + if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + control_group = (uint16_t)value.type.Unsigned_Int; + status = Control_Groups_Element_Set( + pObject, array_index, control_group); + if (status) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_ABORT_OTHER; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + } + + return error_code; +} + /** * WriteProperty handler for this object. For the given WriteProperty * data, the application_data is loaded or the error flags are set. @@ -1543,30 +1239,34 @@ bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) bool status = false; /* return value */ int len = 0; BACNET_APPLICATION_DATA_VALUE value = { 0 }; - int element_len = 0; - uint32_t count = 0; + bool is_array; - /* decode the some of the request */ - len = bacapp_decode_application_data( - wp_data->application_data, wp_data->application_data_len, &value); - /* FIXME: len < application_data_len: more data? */ + /* only array properties can have array options */ + is_array = property_list_bacnet_array_member( + wp_data->object_type, wp_data->object_property); + if (!is_array && (wp_data->array_index != BACNET_ARRAY_ALL)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; + return false; + } + /* decode the first value of the request */ + len = bacapp_decode_known_property( + wp_data->application_data, wp_data->application_data_len, &value, + wp_data->object_type, wp_data->object_property); if (len < 0) { /* error while decoding - a value larger than we can handle */ wp_data->error_class = ERROR_CLASS_PROPERTY; wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; return false; } - if ((wp_data->object_property != PROP_CONTROL_GROUPS) && - (wp_data->object_property != PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES) && - (wp_data->array_index != BACNET_ARRAY_ALL)) { - /* only array properties can have array options */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY; - return false; - } switch (wp_data->object_property) { case PROP_PRESENT_VALUE: - status = Channel_Present_Value_Set(wp_data, &value); + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_CHANNEL_VALUE); + if (status) { + status = Channel_Present_Value_Write( + wp_data, &value.type.Channel_Value); + } break; case PROP_OUT_OF_SERVICE: status = write_property_type_valid( @@ -1577,100 +1277,118 @@ bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) } break; case PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES: - /* FIXME: add property handling */ - /* status = */ - /* Channel_List_Of_Object_Property_References_Set( */ - /* wp_data, */ - /* &value); */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = - ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Channel_List_Of_Object_Property_References_Length, + Channel_List_Of_Object_Property_References_Write, + CHANNEL_MEMBERS_MAX, wp_data->application_data, + wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } break; case PROP_CHANNEL_NUMBER: status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); if (status) { - Channel_Number_Set( - wp_data->object_instance, value.type.Unsigned_Int); - } - break; - case PROP_CONTROL_GROUPS: - status = write_property_type_valid( - wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); - if (status) { - if (wp_data->array_index == 0) { - /* Array element zero is the number of elements in the array - */ - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; - } else if (wp_data->array_index == BACNET_ARRAY_ALL) { - count = CONTROL_GROUPS_MAX; - /* extra elements still encoded in application data */ - element_len = len; - do { - if ((element_len > 0) && - (value.tag == - BACNET_APPLICATION_TAG_UNSIGNED_INT)) { - if ((wp_data->array_index <= CONTROL_GROUPS_MAX) && - (value.type.Unsigned_Int <= 65535)) { - status = Channel_Control_Groups_Element_Set( - wp_data->object_instance, - wp_data->array_index, - value.type.Unsigned_Int); - } - if (!status) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = - ERROR_CODE_VALUE_OUT_OF_RANGE; - break; - } - } - count--; - if (count) { - element_len = bacapp_decode_application_data( - &wp_data->application_data[len], - wp_data->application_data_len - len, &value); - if (element_len < 0) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = - ERROR_CODE_VALUE_OUT_OF_RANGE; - break; - } - len += element_len; - } - } while (count); + if (value.type.Unsigned_Int <= UINT16_MAX) { + Channel_Number_Set( + wp_data->object_instance, value.type.Unsigned_Int); } else { - if ((wp_data->array_index <= CONTROL_GROUPS_MAX) && - (value.type.Unsigned_Int <= 65535)) { - status = Channel_Control_Groups_Element_Set( - wp_data->object_instance, wp_data->array_index, - value.type.Unsigned_Int); - } - if (!status) { - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; - } + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + status = false; } } break; - case PROP_OBJECT_IDENTIFIER: - case PROP_OBJECT_NAME: - case PROP_OBJECT_TYPE: - case PROP_LAST_PRIORITY: - case PROP_WRITE_STATUS: - case PROP_STATUS_FLAGS: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + case PROP_CONTROL_GROUPS: + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Channel_Control_Groups_Length, Channel_Control_Groups_Write, + CONTROL_GROUPS_MAX, wp_data->application_data, + wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } break; default: - wp_data->error_class = ERROR_CLASS_PROPERTY; - wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + if (property_lists_member( + Channel_Properties_Required, Channel_Properties_Optional, + Channel_Properties_Proprietary, wp_data->object_property)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } break; } return status; } +/** + * @brief Callback for WriteGroup-Request iterator + * @param data [in] The contents of the WriteGroup-Request message + * @param change_list_index [in] index of the current value in the change-list + * @param change_list [in] The current value in the change-list + */ +void Channel_Write_Group( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list) +{ + struct object_data *pObject; + unsigned count, g, priority; + uint32_t instance; + int index; + bool status = false, found = false; + + if (!data || !change_list) { + return; + } + (void)change_list_index; + /* iterate through all channels to find + a) matching group number, and + b) matching channel number + Then write the value to the channel */ + count = Keylist_Count(Object_List); + for (index = 0; index < count; index++) { + pObject = Keylist_Data_Index(Object_List, index); + if (!pObject) { + continue; + } + instance = Channel_Index_To_Instance(index); + for (g = 0; g < CONTROL_GROUPS_MAX; g++) { + if (pObject->Control_Groups[g] == 0) { + continue; + } + if ((pObject->Control_Groups[g] == data->group_number) && + (pObject->Channel_Number == change_list->channel)) { + priority = change_list->overriding_priority; + if ((priority > BACNET_MAX_PRIORITY) || + (priority < BACNET_MIN_PRIORITY)) { + priority = data->write_priority; + } + /* note: inhibit delay is ignored because this + implementation does not support the execution-delay + property */ + status = Channel_Write_Members( + pObject, instance, &change_list->value, priority); + if (status) { + pObject->Last_Priority = priority; + } + found = true; + } + } + } + if (!found) { + debug_printf( + "Channel Objects: group_number=%u, channel=%u not found\n", + data->group_number, change_list->channel); + } +} + /** * @brief Sets a callback used when present-value is written from BACnet * @param cb - callback used to provide indications @@ -1701,7 +1419,8 @@ uint32_t Channel_Create(uint32_t object_instance) the object identifier is a local matter.*/ object_instance = Keylist_Next_Empty_Key(Object_List, 1); } - pObject = Keylist_Data(Object_List, object_instance); + + pObject = Object_Data(object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); if (pObject) { @@ -1712,17 +1431,9 @@ uint32_t Channel_Create(uint32_t object_instance) pObject->Last_Priority = BACNET_NO_PRIORITY; pObject->Write_Status = BACNET_WRITE_STATUS_IDLE; for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) { - pObject->Members[m].objectIdentifier.type = - OBJECT_LIGHTING_OUTPUT; - pObject->Members[m].objectIdentifier.instance = - BACNET_MAX_INSTANCE; - pObject->Members[m].propertyIdentifier = PROP_PRESENT_VALUE; - pObject->Members[m].arrayIndex = BACNET_ARRAY_ALL; - pObject->Members[m].deviceIdentifier.type = OBJECT_DEVICE; - pObject->Members[m].deviceIdentifier.instance = - BACNET_MAX_INSTANCE; + List_Of_Object_Property_References_Set(pObject, m, NULL); } - pObject->Number = 0; + pObject->Channel_Number = 0; for (g = 0; g < CONTROL_GROUPS_MAX; g++) { pObject->Control_Groups[g] = 0; } diff --git a/src/bacnet/basic/object/channel.h b/src/bacnet/basic/object/channel.h index 823914fb..a5962cec 100644 --- a/src/bacnet/basic/object/channel.h +++ b/src/bacnet/basic/object/channel.h @@ -15,89 +15,9 @@ /* BACnet Stack API */ #include "bacnet/rp.h" #include "bacnet/wp.h" +#include "bacnet/write_group.h" #include "bacnet/basic/object/lo.h" - -/* BACNET_CHANNEL_VALUE decodes WriteProperty service requests - Choose the datatypes that your application supports */ -#if !( \ - defined(CHANNEL_NUMERIC) || defined(CHANNEL_NULL) || \ - defined(CHANNEL_BOOLEAN) || defined(CHANNEL_UNSIGNED) || \ - defined(CHANNEL_SIGNED) || defined(CHANNEL_REAL) || \ - defined(CHANNEL_DOUBLE) || defined(CHANNEL_OCTET_STRING) || \ - defined(CHANNEL_CHARACTER_STRING) || defined(CHANNEL_BIT_STRING) || \ - defined(CHANNEL_ENUMERATED) || defined(CHANNEL_DATE) || \ - defined(CHANNEL_TIME) || defined(CHANNEL_OBJECT_ID) || \ - defined(CHANNEL_LIGHTING_COMMAND) || defined(CHANNEL_XY_COLOR) || \ - defined(CHANNEL_COLOR_COMMAND)) -#define CHANNEL_NUMERIC -#endif - -#if defined(CHANNEL_NUMERIC) -#define CHANNEL_NULL -#define CHANNEL_BOOLEAN -#define CHANNEL_UNSIGNED -#define CHANNEL_SIGNED -#define CHANNEL_REAL -#define CHANNEL_DOUBLE -#define CHANNEL_ENUMERATED -#define CHANNEL_LIGHTING_COMMAND -#define CHANNEL_COLOR_COMMAND -#define CHANNEL_XY_COLOR -#endif - -typedef struct BACnet_Channel_Value_t { - uint8_t tag; - union { - /* NULL - not needed as it is encoded in the tag alone */ -#if defined(CHANNEL_BOOLEAN) - bool Boolean; -#endif -#if defined(CHANNEL_UNSIGNED) - uint32_t Unsigned_Int; -#endif -#if defined(CHANNEL_SIGNED) - int32_t Signed_Int; -#endif -#if defined(CHANNEL_REAL) - float Real; -#endif -#if defined(CHANNEL_DOUBLE) - double Double; -#endif -#if defined(CHANNEL_OCTET_STRING) - BACNET_OCTET_STRING Octet_String; -#endif -#if defined(CHANNEL_CHARACTER_STRING) - BACNET_CHARACTER_STRING Character_String; -#endif -#if defined(CHANNEL_BIT_STRING) - BACNET_BIT_STRING Bit_String; -#endif -#if defined(CHANNEL_ENUMERATED) - uint32_t Enumerated; -#endif -#if defined(CHANNEL_DATE) - BACNET_DATE Date; -#endif -#if defined(CHANNEL_TIME) - BACNET_TIME Time; -#endif -#if defined(CHANNEL_OBJECT_ID) - BACNET_OBJECT_ID Object_Id; -#endif -#if defined(CHANNEL_LIGHTING_COMMAND) - BACNET_LIGHTING_COMMAND Lighting_Command; -#endif -#if defined(CHANNEL_COLOR_COMMAND) - BACNET_COLOR_COMMAND Color_Command; -#endif -#if defined(CHANNEL_XY_COLOR) - BACNET_XY_COLOR XY_Color; -#endif - } type; - /* simple linked list if needed */ - struct BACnet_Channel_Value_t *next; -} BACNET_CHANNEL_VALUE; +#include "bacnet/channel_value.h" #ifdef __cplusplus extern "C" { @@ -128,13 +48,19 @@ BACNET_STACK_EXPORT int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); BACNET_STACK_EXPORT bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); +BACNET_STACK_EXPORT +void Channel_Write_Group( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list); BACNET_STACK_EXPORT BACNET_CHANNEL_VALUE *Channel_Present_Value(uint32_t object_instance); BACNET_STACK_EXPORT bool Channel_Present_Value_Set( - BACNET_WRITE_PROPERTY_DATA *wp_data, - const BACNET_APPLICATION_DATA_VALUE *value); + uint32_t object_instance, + uint8_t priority, + const BACNET_CHANNEL_VALUE *value); BACNET_STACK_EXPORT bool Channel_Out_Of_Service(uint32_t object_instance); @@ -169,22 +95,10 @@ Channel_Control_Groups_Element(uint32_t object_instance, int32_t array_index); BACNET_STACK_EXPORT bool Channel_Control_Groups_Element_Set( uint32_t object_instance, int32_t array_index, uint16_t value); -BACNET_STACK_EXPORT -bool Channel_Value_Copy( - BACNET_CHANNEL_VALUE *cvalue, const BACNET_APPLICATION_DATA_VALUE *value); -BACNET_STACK_EXPORT -int Channel_Value_Encode( - uint8_t *apdu, int apdu_max, const BACNET_CHANNEL_VALUE *value); -BACNET_STACK_EXPORT -int Channel_Coerce_Data_Encode( - uint8_t *apdu, - size_t apdu_size, - const BACNET_APPLICATION_DATA_VALUE *value, - BACNET_APPLICATION_TAG tag); + BACNET_STACK_EXPORT bool Channel_Write_Member_Value( - BACNET_WRITE_PROPERTY_DATA *wp_data, - const BACNET_APPLICATION_DATA_VALUE *value); + BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_CHANNEL_VALUE *value); BACNET_STACK_EXPORT void Channel_Write_Property_Internal_Callback_Set(write_property_function cb); diff --git a/src/bacnet/basic/service/h_write_group.c b/src/bacnet/basic/service/h_write_group.c new file mode 100644 index 00000000..9b0c3485 --- /dev/null +++ b/src/bacnet/basic/service/h_write_group.c @@ -0,0 +1,111 @@ +/** + * @file + * @brief The WriteGroup-Request service handler + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdcode.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/write_group.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/sys/debug.h" + +/* WriteGroup-Request notification callbacks list */ +static BACNET_WRITE_GROUP_NOTIFICATION Write_Group_Notification_Head; + +/** + * @brief Print the contents of a WriteGroup-Request + * @param data [in] The decoded WriteGroup-Request message + */ +void handler_write_group_print_data(BACNET_WRITE_GROUP_DATA *data) +{ + if (!data) { + return; + } + debug_printf( + "WriteGroup:group-number=%lu\r\n", (unsigned long)data->group_number); + debug_printf( + "WriteGroup:write-priority=%lu\r\n", + (unsigned long)data->write_priority); +} + +/** + * @brief generic callback for WriteGroup-Request iterator + * @param data [in] The contents of the WriteGroup-Request message + * @param change_list_index [in] The index of the current value in the change + * list + * @param change_list [in] The current value in the change list + */ +static void handler_write_group_notification_callback( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list) +{ + BACNET_WRITE_GROUP_NOTIFICATION *head; + + handler_write_group_print_data(data); + head = &Write_Group_Notification_Head; + do { + if (head->callback) { + head->callback(data, change_list_index, change_list); + } + head = head->next; + } while (head); +} + +/** + * @brief Add a WriteGroup notification callback + * @param cb - WriteGroup notification callback to be added + */ +void handler_write_group_notification_add(BACNET_WRITE_GROUP_NOTIFICATION *cb) +{ + BACNET_WRITE_GROUP_NOTIFICATION *head; + + head = &Write_Group_Notification_Head; + do { + if (head->next == cb) { + /* already here! */ + break; + } else if (!head->next) { + /* first available free node */ + head->next = cb; + break; + } + head = head->next; + } while (head); +} + +/** + * @brief A basic WriteGroup-Request service handler + * @param service_request [in] The contents of the service request + * @param service_len [in] The length of the service request + * @param src [in] The source of the message + */ +void handler_write_group( + uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src) +{ + BACNET_WRITE_GROUP_DATA data = { 0 }; + int len = 0; + + (void)src; + debug_printf("Received WriteGroup-Request!\n"); + + len = bacnet_write_group_service_request_decode_iterate( + service_request, service_len, &data, + handler_write_group_notification_callback); + if (len <= 0) { + debug_printf("WriteGroup-Request failed to decode!\n"); + } +} diff --git a/src/bacnet/basic/service/h_write_group.h b/src/bacnet/basic/service/h_write_group.h new file mode 100644 index 00000000..0f93b49e --- /dev/null +++ b/src/bacnet/basic/service/h_write_group.h @@ -0,0 +1,36 @@ +/** + * @file + * @brief The WriteGroup-Request service handler + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef HANDLER_WRITE_GROUP_H +#define HANDLER_WRITE_GROUP_H + +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/apdu.h" +#include "bacnet/write_group.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +void handler_write_group( + uint8_t *service_request, uint16_t service_len, BACNET_ADDRESS *src); +BACNET_STACK_EXPORT +void handler_write_group_print_data(BACNET_WRITE_GROUP_DATA *data); +BACNET_STACK_EXPORT +void handler_write_group_notification_add(BACNET_WRITE_GROUP_NOTIFICATION *cb); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/service/s_write_group.c b/src/bacnet/basic/service/s_write_group.c new file mode 100644 index 00000000..5040de80 --- /dev/null +++ b/src/bacnet/basic/service/s_write_group.c @@ -0,0 +1,62 @@ +/** + * @file + * @brief Header file for a basic BACnet WriteGroup-Reqeust service send + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdcode.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/dcc.h" +#include "bacnet/write_group.h" +/* some demo stuff needed */ +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" + +int Send_Write_Group(BACNET_ADDRESS *dest, const BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + size_t apdu_size; + + if (!dcc_communication_enabled()) { + return bytes_sent; + } + datalink_get_my_address(&my_address); + /* encode the NPDU portion of the packet */ + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], dest, &my_address, &npdu_data); + + /* encode the APDU portion of the packet */ + Handler_Transmit_Buffer[pdu_len++] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST; + Handler_Transmit_Buffer[pdu_len++] = SERVICE_UNCONFIRMED_WRITE_GROUP; + apdu_size = sizeof(Handler_Transmit_Buffer) - pdu_len; + len = bacnet_write_group_service_request_encode( + &Handler_Transmit_Buffer[pdu_len], apdu_size, data); + pdu_len += len; + bytes_sent = datalink_send_pdu( + dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + if (bytes_sent <= 0) { +#if PRINT_ENABLED + fprintf( + stderr, "Failed to Send WriteGroup-Request (%s)!\n", + strerror(errno)); +#endif + } + + return bytes_sent; +} diff --git a/src/bacnet/basic/service/s_write_group.h b/src/bacnet/basic/service/s_write_group.h new file mode 100644 index 00000000..00fdce13 --- /dev/null +++ b/src/bacnet/basic/service/s_write_group.h @@ -0,0 +1,30 @@ +/** + * @file + * @brief Header file for a basic BACnet WriteGroup-Reqeust service send + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_SERVICE_SEND_WRITE_GROUP_H +#define BACNET_SERVICE_SEND_WRITE_GROUP_H + +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/write_group.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int Send_Write_Group(BACNET_ADDRESS *dest, const BACNET_WRITE_GROUP_DATA *data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/services.h b/src/bacnet/basic/services.h index 71d1a540..13140090 100644 --- a/src/bacnet/basic/services.h +++ b/src/bacnet/basic/services.h @@ -52,6 +52,7 @@ #include "bacnet/basic/service/h_whois.h" #include "bacnet/basic/service/h_wp.h" #include "bacnet/basic/service/h_wpm.h" +#include "bacnet/basic/service/h_write_group.h" /* application layer service send helpers */ #include "bacnet/basic/service/s_abort.h" @@ -82,6 +83,7 @@ #include "bacnet/basic/service/s_whois.h" #include "bacnet/basic/service/s_wp.h" #include "bacnet/basic/service/s_wpm.h" +#include "bacnet/basic/service/s_write_group.h" /** @defgroup MISCHNDLR Miscellaneous Service Handlers * Various utilities and functions to support the service handlers diff --git a/src/bacnet/channel_value.c b/src/bacnet/channel_value.c new file mode 100644 index 00000000..2791bf84 --- /dev/null +++ b/src/bacnet/channel_value.c @@ -0,0 +1,1053 @@ +/** + * @file + * @brief BACnet single precision REAL encode and decode functions + * @author Steve Karg + * @date 2004 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdcode.h" +#include "bacnet/bacstr.h" +#include "bacnet/bacint.h" +#include "bacnet/bacreal.h" +#include "bacnet/channel_value.h" + +/** + * @brief Encode a given BACnetChanneValue + * @param apdu - APDU buffer for storing the encoded data, or NULL for length + * @param value - BACNET_CHANNEL_VALUE value + * @return number of bytes in the APDU + */ +int bacnet_channel_value_type_encode( + uint8_t *apdu, const BACNET_CHANNEL_VALUE *value) +{ + int apdu_len = 0; + + if (!value) { + return 0; + } + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + apdu_len = encode_application_null(apdu); + break; +#if defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + apdu_len = encode_application_boolean(apdu, value->type.Boolean); + break; +#endif +#if defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + apdu_len = + encode_application_unsigned(apdu, value->type.Unsigned_Int); + break; +#endif +#if defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + apdu_len = encode_application_signed(apdu, value->type.Signed_Int); + break; +#endif +#if defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + apdu_len = encode_application_real(apdu, value->type.Real); + break; +#endif +#if defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + apdu_len = encode_application_double(apdu, value->type.Double); + break; +#endif +#if defined(CHANNEL_OCTET_STRING) + case BACNET_APPLICATION_TAG_OCTET_STRING: + apdu_len = encode_application_octet_string( + apdu, &value->type.Octet_String); + break; +#endif +#if defined(CHANNEL_CHARACTER_STRING) + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + apdu_len = encode_application_character_string( + apdu, &value->type.Character_String); + break; +#endif +#if defined(CHANNEL_BIT_STRING) + case BACNET_APPLICATION_TAG_BIT_STRING: + apdu_len = + encode_application_bitstring(apdu, &value->type.Bit_String); + break; +#endif +#if defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + apdu_len = + encode_application_enumerated(apdu, value->type.Enumerated); + break; +#endif +#if defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + apdu_len = encode_application_date(apdu, &value->type.Date); + break; +#endif +#if defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + apdu_len = encode_application_time(apdu, &value->type.Time); + break; +#endif +#if defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + apdu_len = encode_application_object_id( + apdu, (int)value->type.Object_Id.type, + value->type.Object_Id.instance); + break; +#endif +#if defined(CHANNEL_LIGHTING_COMMAND) + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: + apdu_len = lighting_command_encode_context( + apdu, 0, &value->type.Lighting_Command); + break; +#endif +#if defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + apdu_len = color_command_context_encode( + apdu, 1, &value->type.Color_Command); + break; +#endif +#if defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + apdu_len = xy_color_context_encode(apdu, 2, &value->type.XY_Color); + break; +#endif + default: + break; + } + + return apdu_len; +} + +/** + * @brief Decode a BACnet channel value + */ +int bacnet_channel_value_type_decode( + const uint8_t *apdu, + size_t apdu_size, + uint8_t tag_data_type, + uint32_t len_value_type, + BACNET_CHANNEL_VALUE *value) +{ + int len = 0; + + if (!apdu) { + return BACNET_STATUS_ERROR; + } + if (!value) { + return BACNET_STATUS_ERROR; + } + switch (tag_data_type) { + case BACNET_APPLICATION_TAG_NULL: + /* nothing else to do */ + break; +#if defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + value->type.Boolean = decode_boolean(len_value_type); + break; +#endif +#if defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + len = bacnet_unsigned_decode( + apdu, apdu_size, len_value_type, &value->type.Unsigned_Int); + break; +#endif +#if defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + len = bacnet_signed_decode( + apdu, apdu_size, len_value_type, &value->type.Signed_Int); + break; +#endif +#if defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + len = bacnet_real_decode( + apdu, apdu_size, len_value_type, &(value->type.Real)); + break; +#endif +#if defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + len = bacnet_double_decode( + apdu, apdu_size, len_value_type, &(value->type.Double)); + break; +#endif +#if defined(CHANNEL_OCTET_STRING) + case BACNET_APPLICATION_TAG_OCTET_STRING: + len = bacnet_octet_string_decode( + apdu, apdu_size, len_value_type, &value->type.Octet_String); + break; +#endif +#if defined(CHANNEL_CHARACTER_STRING) + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + len = bacnet_character_string_decode( + apdu, apdu_size, len_value_type, &value->type.Character_String); + break; +#endif +#if defined(CHANNEL_BIT_STRING) + case BACNET_APPLICATION_TAG_BIT_STRING: + len = bacnet_bitstring_decode( + apdu, apdu_size, len_value_type, &value->type.Bit_String); + break; +#endif +#if defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + len = bacnet_enumerated_decode( + apdu, apdu_size, len_value_type, &value->type.Enumerated); + break; +#endif +#if defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + len = bacnet_date_decode( + apdu, apdu_size, len_value_type, &value->type.Date); + break; +#endif +#if defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + len = bacnet_time_decode( + apdu, apdu_size, len_value_type, &value->type.Time); + break; +#endif +#if defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + len = bacnet_object_id_decode( + apdu, apdu_size, len_value_type, &value->type.Object_Id.type, + &value->type.Object_Id.instance); + break; +#endif + default: + len = BACNET_STATUS_ERROR; + break; + } + if ((len == 0) && (tag_data_type != BACNET_APPLICATION_TAG_NULL) && + (tag_data_type != BACNET_APPLICATION_TAG_BOOLEAN) && + (tag_data_type != BACNET_APPLICATION_TAG_OCTET_STRING)) { + /* indicate that we were not able to decode the value */ + len = BACNET_STATUS_ERROR; + } + if (len != BACNET_STATUS_ERROR) { + value->tag = tag_data_type; + } + + return len; +} + +/** + * @brief Encode a given channel value + * @param apdu - APDU buffer for storing the encoded data, or NULL for length + * @param apdu_size - size of the APDU buffer + * @param value - BACNET_CHANNEL_VALUE value + * @return number of bytes in the APDU, or BACNET_STATUS_ERROR + */ +int bacnet_channel_value_encode( + uint8_t *apdu, size_t apdu_size, const BACNET_CHANNEL_VALUE *value) +{ + size_t apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = bacnet_channel_value_type_encode(NULL, value); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = bacnet_channel_value_type_encode(apdu, value); + } + + return apdu_len; +} + +/** + * @brief Decode a given channel value + * @param apdu - APDU buffer for decoding + * @param apdu_size - Count of valid bytes in the buffer + * @param value - BACNET_CHANNEL_VALUE value to store the decoded data + * @return number of bytes decoded or BACNET_STATUS_ERROR on error + */ +int bacnet_channel_value_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_CHANNEL_VALUE *value) +{ + int len = 0; + int apdu_len = 0; + BACNET_TAG tag = { 0 }; + + if (!apdu) { + return BACNET_STATUS_ERROR; + } + if (!value) { + return BACNET_STATUS_ERROR; + } + len = bacnet_tag_decode(&apdu[apdu_len], apdu_size - apdu_len, &tag); + if (len > 0) { + apdu_len += len; + if (tag.application) { + len = bacnet_channel_value_type_decode( + &apdu[apdu_len], apdu_size - apdu_len, tag.number, + tag.len_value_type, value); + if (len >= 0) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + } else if (tag.opening) { + len = 0; + switch (tag.number) { + case 0: + value->tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND; +#if defined(CHANNEL_LIGHTING_COMMAND) + len = lighting_command_decode( + &apdu[apdu_len], apdu_size - apdu_len, + &value->type.Lighting_Command); +#endif + break; + case 1: + value->tag = BACNET_APPLICATION_TAG_COLOR_COMMAND; +#if defined(CHANNEL_COLOR_COMMAND) + len = color_command_decode( + &apdu[apdu_len], apdu_size - apdu_len, NULL, + &value->type.Color_Command); +#endif + break; + case 2: + value->tag = BACNET_APPLICATION_TAG_XY_COLOR; +#if defined(CHANNEL_XY_COLOR) + len = xy_color_decode( + &apdu[apdu_len], apdu_size - apdu_len, + &value->type.XY_Color); +#endif + break; + default: + return BACNET_STATUS_ERROR; + } + if (len > 0) { + apdu_len += len; + if (bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, tag.number, + &len)) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + } else { + return BACNET_STATUS_ERROR; + } + } else { + return BACNET_STATUS_ERROR; + } + } else { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Compare two BACnetChannelValue values + * @param value1 [in] The first BACnetChannelValue value + * @param value2 [in] The second BACnetChannelValue value + * @return True if the values are the same, else False + */ +bool bacnet_channel_value_same( + const BACNET_CHANNEL_VALUE *value1, const BACNET_CHANNEL_VALUE *value2) +{ + if (value1->tag != value2->tag) { + return false; + } + switch (value1->tag) { + case BACNET_APPLICATION_TAG_NULL: + return true; +#if defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + return value1->type.Boolean == value2->type.Boolean; +#endif +#if defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + return value1->type.Unsigned_Int == value2->type.Unsigned_Int; +#endif +#if defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + return value1->type.Signed_Int == value2->type.Signed_Int; +#endif +#if defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + return !islessgreater(value1->type.Real, value2->type.Real); +#endif +#if defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + return !islessgreater(value1->type.Double, value2->type.Double); +#endif +#if defined(CHANNEL_OCTET_STRING) + case BACNET_APPLICATION_TAG_OCTET_STRING: + return octetstring_value_same( + &value1->type.Octet_String, &value2->type.Octet_String); +#endif +#if defined(CHANNEL_CHARACTER_STRING) + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + return characterstring_value_same( + &value1->type.Character_String, &value2->type.Character_String); +#endif +#if defined(CHANNEL_BIT_STRING) + case BACNET_APPLICATION_TAG_BIT_STRING: + return bitstring_value_same( + &value1->type.Bit_String, &value2->type.Bit_String); +#endif +#if defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + return value1->type.Enumerated == value2->type.Enumerated; +#endif +#if defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + return date_value_same(&value1->type.Date, &value2->type.Date); +#endif +#if defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + return time_value_same(&value1->type.Time, &value2->type.Time); +#endif +#if defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + return object_id_value_same( + &value1->type.Object_Id, &value2->type.Object_Id); +#endif +#if defined(CHANNEL_LIGHTING_COMMAND) + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: + return lighting_command_same( + &value1->type.Lighting_Command, &value2->type.Lighting_Command); +#endif +#if defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + return color_command_same( + &value1->type.Color_Command, &value2->type.Color_Command); +#endif +#if defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + return xy_color_same( + &value1->type.XY_Color, &value2->type.XY_Color); +#endif + default: + break; + } + + return true; +} + +/** + * @brief Copy a BACnetChannelValue to another + * @param value1 [in] The first BACnetChannelValue value + * @param value2 [in] The second BACnetChannelValue value + * @return true if the value was copied, else false + */ +bool bacnet_channel_value_copy( + BACNET_CHANNEL_VALUE *dest, const BACNET_CHANNEL_VALUE *src) +{ + if (!dest || !src) { + return false; + } + dest->tag = src->tag; + switch (src->tag) { + case BACNET_APPLICATION_TAG_NULL: + return true; +#if defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + dest->type.Boolean = src->type.Boolean; + return true; +#endif +#if defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + dest->type.Unsigned_Int = src->type.Unsigned_Int; + return true; +#endif +#if defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + dest->type.Signed_Int = src->type.Signed_Int; + return true; +#endif +#if defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + dest->type.Real = src->type.Real; + return true; +#endif +#if defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + dest->type.Double = src->type.Double; + return true; +#endif +#if defined(CHANNEL_OCTET_STRING) + case BACNET_APPLICATION_TAG_OCTET_STRING: + return octetstring_copy( + &dest->type.Octet_String, &src->type.Octet_String); +#endif +#if defined(CHANNEL_CHARACTER_STRING) + case BACNET_APPLICATION_TAG_CHARACTER_STRING: + return characterstring_copy( + &dest->type.Character_String, &src->type.Character_String); +#endif +#if defined(CHANNEL_BIT_STRING) + case BACNET_APPLICATION_TAG_BIT_STRING: + return bitstring_copy( + &dest->type.Bit_String, &src->type.Bit_String); +#endif +#if defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + dest->type.Enumerated = src->type.Enumerated; + return true; +#endif +#if defined(CHANNEL_DATE) + case BACNET_APPLICATION_TAG_DATE: + return datetime_copy_date(&dest->type.Date, &src->type.Date); +#endif +#if defined(CHANNEL_TIME) + case BACNET_APPLICATION_TAG_TIME: + return datetime_copy_time(&dest->type.Time, &src->type.Time); +#endif +#if defined(CHANNEL_OBJECT_ID) + case BACNET_APPLICATION_TAG_OBJECT_ID: + dest->type.Object_Id.type = src->type.Object_Id.type; + dest->type.Object_Id.instance = src->type.Object_Id.instance; + return true; +#endif +#if defined(CHANNEL_LIGHTING_COMMAND) + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: + return lighting_command_copy( + &dest->type.Lighting_Command, &src->type.Lighting_Command); +#endif +#if defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + return color_command_copy( + &dest->type.Color_Command, &src->type.Color_Command); +#endif +#if defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + return xy_color_copy(&dest->type.XY_Color, &src->type.XY_Color); +#endif + default: + break; + } + + return false; +} + +/** + * @brief Parse a string into a BACnetChannelValue structure + * @param value [out] The BACnetChannelValue value + * @param argv [in] The string to parse + * @return true on success, else false + */ +bool bacnet_channel_value_from_ascii( + BACNET_CHANNEL_VALUE *value, const char *argv) +{ + bool status = false; + int count; + unsigned long unsigned_value; + long signed_value; + float single_value; + double double_value; + const char *negative; + const char *decimal_point; + const char *lighting_command; + const char *color_command; + const char *xy_color; + const char *real_string; + const char *double_string; + + if (!value || !argv) { + return false; + } + if (!status) { + if (strcasecmp(argv, "null") == 0) { + value->tag = BACNET_APPLICATION_TAG_NULL; + status = true; + } + } + if (!status) { + if (strcasecmp(argv, "true") == 0) { + value->tag = BACNET_APPLICATION_TAG_BOOLEAN; +#if defined(CHANNEL_BOOLEAN) + value->type.Boolean = true; +#endif + status = true; + } + } + if (!status) { + if (strcasecmp(argv, "false") == 0) { + value->tag = BACNET_APPLICATION_TAG_BOOLEAN; +#if defined(CHANNEL_BOOLEAN) + value->type.Boolean = false; +#endif + status = true; + } + } + if (!status) { + lighting_command = strchr(argv, 'L'); + if (!lighting_command) { + lighting_command = strchr(argv, 'l'); + } + if (lighting_command) { + value->tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND; +#if defined(CHANNEL_LIGHTING_COMMAND) + status = lighting_command_from_ascii( + &value->type.Lighting_Command, argv + 1); +#endif + } + } + if (!status) { + color_command = strchr(argv, 'C'); + if (!color_command) { + color_command = strchr(argv, 'c'); + } + if (color_command) { + value->tag = BACNET_APPLICATION_TAG_COLOR_COMMAND; +#if defined(CHANNEL_COLOR_COMMAND) + /* FIXME: add parsing for BACnetColorCommand */ +#endif + status = true; + } + } + if (!status) { + xy_color = strchr(argv, 'X'); + if (!xy_color) { + xy_color = strchr(argv, 'x'); + } + if (xy_color) { + value->tag = BACNET_APPLICATION_TAG_XY_COLOR; +#if defined(CHANNEL_XY_COLOR) + status = xy_color_from_ascii(&value->type.XY_Color, argv + 1); +#endif + } + } + if (!status) { + real_string = strchr(argv, 'F'); + if (!real_string) { + real_string = strchr(argv, 'f'); + } + if (real_string) { + value->tag = BACNET_APPLICATION_TAG_REAL; + count = sscanf(argv + 1, "%f", &single_value); + if (count == 1) { +#if defined(CHANNEL_REAL) + value->type.Real = single_value; +#endif + status = true; + } + } + } + if (!status) { + double_string = strchr(argv, 'D'); + if (!double_string) { + double_string = strchr(argv, 'd'); + } + if (double_string) { + count = sscanf(argv + 1, "%lf", &double_value); + if (count == 1) { + value->tag = BACNET_APPLICATION_TAG_DOUBLE; +#if defined(CHANNEL_DOUBLE) + value->type.Double = double_value; +#endif + status = true; + } + } + } + if (!status) { + decimal_point = strchr(argv, '.'); + if (decimal_point) { + count = sscanf(argv, "%lf", &double_value); + if (count == 1) { + if (isgreaterequal(double_value, -FLT_MAX) && + islessequal(double_value, FLT_MAX)) { + value->tag = BACNET_APPLICATION_TAG_REAL; +#if defined(CHANNEL_REAL) + value->type.Real = (float)double_value; +#endif + } else { + value->tag = BACNET_APPLICATION_TAG_DOUBLE; +#if defined(CHANNEL_DOUBLE) + value->type.Double = double_value; +#endif + } + status = true; + } + } + } + if (!status) { + negative = strchr(argv, '-'); + if (negative) { + count = sscanf(argv, "%ld", &signed_value); + if (count == 1) { + value->tag = BACNET_APPLICATION_TAG_SIGNED_INT; +#if defined(CHANNEL_SIGNED) + value->type.Signed_Int = signed_value; +#endif + status = true; + } + } + } + if (!status) { + count = sscanf(argv, "%lu", &unsigned_value); + if (count == 1) { + value->tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; +#if defined(CHANNEL_UNSIGNED) + value->type.Unsigned_Int = unsigned_value; +#endif + status = true; + } + } + + return status; +} + +/** + * @brief Convert an array of BACnetChannelValue to linked list + * @param array pointer to element zero of the array + * @param size number of elements in the array + */ +void bacnet_channel_value_link_array(BACNET_CHANNEL_VALUE *array, size_t size) +{ + size_t i = 0; + + for (i = 0; i < size; i++) { + if (i > 0) { + array[i - 1].next = &array[i]; + } + array[i].next = NULL; + } +} + +/** + * For a given application value, coerce the encoding, if necessary + * + * @param apdu - buffer to hold the encoding, or NULL for length + * @param value - BACNET_APPLICATION_DATA_VALUE value + * @param tag - application tag to be coerced, if possible + * + * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. + */ +static int channel_value_coerce_data_encode( + uint8_t *apdu, + const BACNET_CHANNEL_VALUE *value, + BACNET_APPLICATION_TAG tag) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + float float_value = 0.0; + double double_value = 0.0; + uint32_t unsigned_value = 0; + int32_t signed_value = 0; + bool boolean_value = false; + + if (!value) { + return BACNET_STATUS_ERROR; + } + switch (value->tag) { + case BACNET_APPLICATION_TAG_NULL: + if ((tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) || + (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND)) { + apdu_len = BACNET_STATUS_ERROR; + } else { + /* no coercion */ + if (apdu) { + *apdu = value->tag; + } + apdu_len++; + } + break; +#if defined(CHANNEL_BOOLEAN) + case BACNET_APPLICATION_TAG_BOOLEAN: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + apdu_len = + encode_application_boolean(apdu, value->type.Boolean); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if (value->type.Boolean) { + unsigned_value = 1; + } + apdu_len = encode_application_unsigned(apdu, unsigned_value); + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if (value->type.Boolean) { + signed_value = 1; + } + apdu_len = encode_application_signed(apdu, signed_value); + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + if (value->type.Boolean) { + float_value = 1; + } + apdu_len = encode_application_real(apdu, float_value); + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + if (value->type.Boolean) { + double_value = 1; + } + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + if (value->type.Boolean) { + unsigned_value = 1; + } + apdu_len = encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_UNSIGNED) + case BACNET_APPLICATION_TAG_UNSIGNED_INT: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (value->type.Unsigned_Int) { + boolean_value = true; + } + apdu_len = encode_application_boolean(apdu, boolean_value); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + unsigned_value = value->type.Unsigned_Int; + apdu_len = encode_application_unsigned(apdu, unsigned_value); + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if (value->type.Unsigned_Int <= 2147483647) { + signed_value = value->type.Unsigned_Int; + apdu_len = encode_application_signed(apdu, signed_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + if (value->type.Unsigned_Int <= 9999999) { + float_value = (float)value->type.Unsigned_Int; + apdu_len = encode_application_real(apdu, float_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = (double)value->type.Unsigned_Int; + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + unsigned_value = value->type.Unsigned_Int; + apdu_len = encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_SIGNED) + case BACNET_APPLICATION_TAG_SIGNED_INT: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (value->type.Signed_Int) { + boolean_value = true; + } + apdu_len = encode_application_boolean(apdu, boolean_value); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value->type.Signed_Int >= 0) && + (value->type.Signed_Int <= 2147483647)) { + unsigned_value = value->type.Signed_Int; + apdu_len = + encode_application_unsigned(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + signed_value = value->type.Signed_Int; + apdu_len = encode_application_signed(apdu, signed_value); + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + if (value->type.Signed_Int <= 9999999) { + float_value = (float)value->type.Signed_Int; + apdu_len = encode_application_real(apdu, float_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = (double)value->type.Signed_Int; + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + unsigned_value = value->type.Signed_Int; + apdu_len = encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_REAL) + case BACNET_APPLICATION_TAG_REAL: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (islessgreater(value->type.Real, 0.0F)) { + boolean_value = true; + } + apdu_len = encode_application_boolean(apdu, boolean_value); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value->type.Real >= 0.0F) && + (value->type.Real <= 2147483000.0F)) { + unsigned_value = (uint32_t)value->type.Real; + apdu_len = + encode_application_unsigned(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if ((value->type.Real >= -2147483000.0F) && + (value->type.Real <= 214783000.0F)) { + signed_value = (int32_t)value->type.Real; + apdu_len = encode_application_signed(apdu, signed_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + float_value = value->type.Real; + apdu_len = encode_application_real(apdu, float_value); + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = value->type.Real; + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + if ((value->type.Real >= 0.0F) && + (value->type.Real <= 2147483000.0F)) { + unsigned_value = (uint32_t)value->type.Real; + apdu_len = + encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_DOUBLE) + case BACNET_APPLICATION_TAG_DOUBLE: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (islessgreater(value->type.Double, 0.0)) { + boolean_value = true; + } + apdu_len = encode_application_boolean(apdu, boolean_value); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + if ((value->type.Double >= 0.0) && + (value->type.Double <= 2147483000.0)) { + unsigned_value = (uint32_t)value->type.Double; + apdu_len = + encode_application_unsigned(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if ((value->type.Double >= -2147483000.0) && + (value->type.Double <= 214783000.0)) { + signed_value = (int32_t)value->type.Double; + apdu_len = encode_application_signed(apdu, signed_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + if ((value->type.Double >= 3.4E-38) && + (value->type.Double <= 3.4E+38)) { + float_value = (float)value->type.Double; + apdu_len = encode_application_real(apdu, float_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = value->type.Double; + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + if ((value->type.Double >= 0.0) && + (value->type.Double <= 2147483000.0)) { + unsigned_value = (uint32_t)value->type.Double; + apdu_len = + encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_ENUMERATED) + case BACNET_APPLICATION_TAG_ENUMERATED: + if (tag == BACNET_APPLICATION_TAG_BOOLEAN) { + if (value->type.Enumerated) { + boolean_value = true; + } + apdu_len = encode_application_boolean(apdu, boolean_value); + } else if (tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) { + unsigned_value = value->type.Enumerated; + apdu_len = encode_application_unsigned(apdu, unsigned_value); + } else if (tag == BACNET_APPLICATION_TAG_SIGNED_INT) { + if (value->type.Enumerated <= 2147483647) { + signed_value = value->type.Enumerated; + apdu_len = encode_application_signed(apdu, signed_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_REAL) { + if (value->type.Enumerated <= 9999999) { + float_value = (float)value->type.Enumerated; + apdu_len = encode_application_real(apdu, float_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + } else if (tag == BACNET_APPLICATION_TAG_DOUBLE) { + double_value = (double)value->type.Enumerated; + apdu_len = encode_application_double(apdu, double_value); + } else if (tag == BACNET_APPLICATION_TAG_ENUMERATED) { + unsigned_value = value->type.Enumerated; + apdu_len = encode_application_enumerated(apdu, unsigned_value); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_LIGHTING_COMMAND) + case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: + if (tag == BACNET_APPLICATION_TAG_LIGHTING_COMMAND) { + apdu_len = lighting_command_encode( + apdu, &value->type.Lighting_Command); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_COLOR_COMMAND) + case BACNET_APPLICATION_TAG_COLOR_COMMAND: + if (tag == BACNET_APPLICATION_TAG_COLOR_COMMAND) { + apdu_len = + color_command_encode(apdu, &value->type.Color_Command); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif +#if defined(CHANNEL_XY_COLOR) + case BACNET_APPLICATION_TAG_XY_COLOR: + if (tag == BACNET_APPLICATION_TAG_XY_COLOR) { + apdu_len = xy_color_encode(apdu, &value->type.XY_Color); + } else { + apdu_len = BACNET_STATUS_ERROR; + } + break; +#endif + default: + apdu_len = BACNET_STATUS_ERROR; + break; + } + + return apdu_len; +} + +/** + * For a given application value, coerce the encoding, if necessary + * + * @param apdu - buffer to hold the encoding, or null for length + * @param value - BACNET_APPLICATION_DATA_VALUE value + * @param tag - application tag to be coerced, if possible + * + * @return number of bytes in the APDU, or BACNET_STATUS_ERROR if error. + */ +int bacnet_channel_value_coerce_data_encode( + uint8_t *apdu, + size_t apdu_size, + const BACNET_CHANNEL_VALUE *value, + BACNET_APPLICATION_TAG tag) +{ + int len; + + len = channel_value_coerce_data_encode(NULL, value, tag); + if ((len > 0) && (len <= apdu_size)) { + len = channel_value_coerce_data_encode(apdu, value, tag); + } else { + len = BACNET_STATUS_ERROR; + } + + return len; +} diff --git a/src/bacnet/channel_value.h b/src/bacnet/channel_value.h new file mode 100644 index 00000000..f82e1565 --- /dev/null +++ b/src/bacnet/channel_value.h @@ -0,0 +1,146 @@ +/** + * @file + * @brief BACnet single precision REAL encode and decode functions + * @author Steve Karg + * @date 2012 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_CHANNEL_VALUE_H +#define BACNET_CHANNEL_VALUE_H + +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/lighting.h" +#include "bacnet/lighting.h" + +/* BACNET_CHANNEL_VALUE decodes WriteProperty service requests + Choose the datatypes that your application supports */ +#if !( \ + defined(CHANNEL_NUMERIC) || defined(CHANNEL_NULL) || \ + defined(CHANNEL_BOOLEAN) || defined(CHANNEL_UNSIGNED) || \ + defined(CHANNEL_SIGNED) || defined(CHANNEL_REAL) || \ + defined(CHANNEL_DOUBLE) || defined(CHANNEL_OCTET_STRING) || \ + defined(CHANNEL_CHARACTER_STRING) || defined(CHANNEL_BIT_STRING) || \ + defined(CHANNEL_ENUMERATED) || defined(CHANNEL_DATE) || \ + defined(CHANNEL_TIME) || defined(CHANNEL_OBJECT_ID) || \ + defined(CHANNEL_LIGHTING_COMMAND) || defined(CHANNEL_XY_COLOR) || \ + defined(CHANNEL_COLOR_COMMAND)) +#define CHANNEL_NUMERIC +#endif + +#if defined(CHANNEL_NUMERIC) +#define CHANNEL_NULL +#define CHANNEL_BOOLEAN +#define CHANNEL_UNSIGNED +#define CHANNEL_SIGNED +#define CHANNEL_REAL +#define CHANNEL_DOUBLE +#define CHANNEL_ENUMERATED +#define CHANNEL_LIGHTING_COMMAND +#define CHANNEL_COLOR_COMMAND +#define CHANNEL_XY_COLOR +#endif + +typedef struct BACnet_Channel_Value_t { + uint8_t tag; + union { + /* NULL - not needed as it is encoded in the tag alone */ +#if defined(CHANNEL_BOOLEAN) + bool Boolean; +#endif +#if defined(CHANNEL_UNSIGNED) + BACNET_UNSIGNED_INTEGER Unsigned_Int; +#endif +#if defined(CHANNEL_SIGNED) + int32_t Signed_Int; +#endif +#if defined(CHANNEL_REAL) + float Real; +#endif +#if defined(CHANNEL_DOUBLE) + double Double; +#endif +#if defined(CHANNEL_OCTET_STRING) + BACNET_OCTET_STRING Octet_String; +#endif +#if defined(CHANNEL_CHARACTER_STRING) + BACNET_CHARACTER_STRING Character_String; +#endif +#if defined(CHANNEL_BIT_STRING) + BACNET_BIT_STRING Bit_String; +#endif +#if defined(CHANNEL_ENUMERATED) + uint32_t Enumerated; +#endif +#if defined(CHANNEL_DATE) + BACNET_DATE Date; +#endif +#if defined(CHANNEL_TIME) + BACNET_TIME Time; +#endif +#if defined(CHANNEL_OBJECT_ID) + BACNET_OBJECT_ID Object_Id; +#endif +#if defined(CHANNEL_LIGHTING_COMMAND) + BACNET_LIGHTING_COMMAND Lighting_Command; +#endif +#if defined(CHANNEL_COLOR_COMMAND) + BACNET_COLOR_COMMAND Color_Command; +#endif +#if defined(CHANNEL_XY_COLOR) + BACNET_XY_COLOR XY_Color; +#endif + } type; + /* simple linked list if needed */ + struct BACnet_Channel_Value_t *next; +} BACNET_CHANNEL_VALUE; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int bacnet_channel_value_type_encode( + uint8_t *apdu, const BACNET_CHANNEL_VALUE *value); +BACNET_STACK_EXPORT +int bacnet_channel_value_type_decode( + const uint8_t *apdu, + size_t apdu_size, + uint8_t tag_data_type, + uint32_t len_value_type, + BACNET_CHANNEL_VALUE *value); + +BACNET_STACK_EXPORT +int bacnet_channel_value_decode( + const uint8_t *apdu, size_t apdu_len, BACNET_CHANNEL_VALUE *value); +BACNET_STACK_EXPORT +int bacnet_channel_value_encode( + uint8_t *apdu, size_t apdu_size, const BACNET_CHANNEL_VALUE *value); + +BACNET_STACK_EXPORT +bool bacnet_channel_value_from_ascii( + BACNET_CHANNEL_VALUE *value, const char *argv); +BACNET_STACK_EXPORT +bool bacnet_channel_value_copy( + BACNET_CHANNEL_VALUE *dest, const BACNET_CHANNEL_VALUE *src); +BACNET_STACK_EXPORT +bool bacnet_channel_value_same( + const BACNET_CHANNEL_VALUE *value1, const BACNET_CHANNEL_VALUE *value2); + +BACNET_STACK_EXPORT +void bacnet_channel_value_link_array(BACNET_CHANNEL_VALUE *array, size_t size); + +BACNET_STACK_EXPORT +int bacnet_channel_value_coerce_data_encode( + uint8_t *apdu, + size_t apdu_size, + const BACNET_CHANNEL_VALUE *value, + BACNET_APPLICATION_TAG tag); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/config.h b/src/bacnet/config.h index e8218c91..68fe8948 100644 --- a/src/bacnet/config.h +++ b/src/bacnet/config.h @@ -204,6 +204,7 @@ defined(BACAPP_SCALE) || \ defined(BACAPP_SHED_LEVEL) || \ defined(BACAPP_ACCESS_RULE) || \ + defined(BACAPP_CHANNEL_VALUE) || \ defined(BACAPP_TYPES_EXTRA)) #define BACAPP_ALL #endif @@ -251,6 +252,7 @@ #define BACAPP_SCALE #define BACAPP_SHED_LEVEL #define BACAPP_ACCESS_RULE +#define BACAPP_CHANNEL_VALUE #endif /* clang-format off */ @@ -273,7 +275,8 @@ defined(BACAPP_ACTION_COMMAND) || \ defined(BACAPP_SCALE) || \ defined(BACAPP_SHED_LEVEL) || \ - defined(BACAPP_ACCESS_RULE) + defined(BACAPP_ACCESS_RULE) || \ + defined(BACAPP_CHANNEL_VALUE) #define BACAPP_COMPLEX_TYPES #endif /* clang-format on */ diff --git a/src/bacnet/write_group.c b/src/bacnet/write_group.c new file mode 100644 index 00000000..d264b5fb --- /dev/null +++ b/src/bacnet/write_group.c @@ -0,0 +1,700 @@ +/** + * @file + * @brief WriteGroup service encode and decode + * @author Steve Karg + * @date August 2023 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacapp.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/write_group.h" + +/** + * @brief Encode the WriteGroup service request + * + * WriteGroup-Request ::= SEQUENCE { + * group-number [0] Unsigned32, + * write-priority [1] Unsigned (1..16), + * change-list [2] SEQUENCE OF BACnetGroupChannelValue, + * inhibit-delay [3] BOOLEAN OPTIONAL + * } + * + * @param apdu Pointer to the buffer for encoded values + * @param data Pointer to the service data used for encoding values + * + * @return Bytes encoded or zero on error. + */ +int bacnet_write_group_encode( + uint8_t *apdu, const BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + BACNET_UNSIGNED_INTEGER unsigned_value; + + if (!data) { + return 0; + } + /* group-number [0] Unsigned32 */ + len = encode_context_unsigned(apdu, 0, data->group_number); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* write-priority [1] Unsigned (1..16) */ + unsigned_value = data->write_priority; + len = encode_context_unsigned(apdu, 1, unsigned_value); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* change-list [2] SEQUENCE OF BACnetGroupChannelValue */ + len = encode_opening_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* SEQUENCE OF BACnetGroupChannelValue */ + len = bacnet_group_channel_value_encode(apdu, &data->change_list); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_closing_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* inhibit-delay [3] BOOLEAN OPTIONAL */ + if (data->inhibit_delay == WRITE_GROUP_INHIBIT_DELAY_TRUE) { + len = encode_context_boolean(apdu, 3, true); + apdu_len += len; + } else if (data->inhibit_delay == WRITE_GROUP_INHIBIT_DELAY_FALSE) { + len = encode_context_boolean(apdu, 3, false); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Encode the WriteGroup service request + * @param apdu Pointer to the buffer for encoding into + * @param apdu_size number of bytes available in the buffer + * @param data Pointer to the service data used for encoding values + * @return number of bytes encoded, or zero if unable to encode or too large + */ +size_t bacnet_write_group_service_request_encode( + uint8_t *apdu, size_t apdu_size, const BACNET_WRITE_GROUP_DATA *data) +{ + size_t apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = bacnet_write_group_encode(NULL, data); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = bacnet_write_group_encode(apdu, data); + } + + return apdu_len; +} + +static int write_group_service_group_number_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + BACNET_UNSIGNED_INTEGER unsigned_value; + + /* group-number [0] Unsigned32 */ + len = bacnet_unsigned_context_decode(apdu, apdu_size, 0, &unsigned_value); + if (len > 0) { + /* This parameter is an unsigned integer in the + range 1 – 4294967295 that represents the control + group to be affected by this request. + Control group zero shall never be used + and shall be reserved. WriteGroup service + requests containing a zero value for + 'Group Number' shall be ignored.*/ + if ((unsigned_value > 4294967295) || (unsigned_value < 1)) { + return BACNET_STATUS_ERROR; + } + if (data) { + data->group_number = (uint32_t)unsigned_value; + } + } else { + return BACNET_STATUS_ERROR; + } + + return len; +} + +static int write_group_service_write_priority_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + BACNET_UNSIGNED_INTEGER unsigned_value; + + /* write-priority [1] Unsigned (1..16) */ + len = bacnet_unsigned_context_decode(apdu, apdu_size, 1, &unsigned_value); + if (len > 0) { + /* This parameter is an unsigned integer in the range 1..16 + that represents the priority for writing that shall apply + to any channel value changes that result in writes to properties + of BACnet objects. */ + if ((unsigned_value > BACNET_MAX_PRIORITY) || + (unsigned_value < BACNET_MIN_PRIORITY)) { + return BACNET_STATUS_ERROR; + } + if (data) { + data->write_priority = (uint8_t)unsigned_value; + } + } else { + return BACNET_STATUS_ERROR; + } + + return len; +} + +static int write_group_service_inhibit_delay_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + bool boolean_value = false; + + /* inhibit-delay [3] BOOLEAN OPTIONAL */ + len = bacnet_boolean_context_decode(apdu, apdu_size, 3, &boolean_value); + if (len > 0) { + if (data) { + if (boolean_value) { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_TRUE; + } else { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_FALSE; + } + } + } else { + return BACNET_STATUS_ERROR; + } + + return len; +} + +static int write_group_service_change_list_decode( + const uint8_t *apdu, + size_t apdu_size, + BACNET_WRITE_GROUP_DATA *data, + BACnet_Write_Group_Callback callback) +{ + int len = 0; + int apdu_len = 0; + BACNET_GROUP_CHANNEL_VALUE change_value = { 0 }; + uint32_t change_list_index = 0; + bool closed = false; + + /* change-list [2] SEQUENCE OF BACnetGroupChannelValue */ + if (bacnet_is_opening_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 2, &len)) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + while (apdu_len < apdu_size) { + if (bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 2, &len)) { + /* end of change-list [2] SEQUENCE OF BACnetGroupChannelValue */ + apdu_len += len; + closed = true; + break; + } + len = bacnet_group_channel_value_decode( + &apdu[apdu_len], apdu_size - apdu_len, &change_value); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + if (callback) { + callback(data, change_list_index, &change_value); + } + change_list_index++; + } + if (!closed) { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief generic callback for WriteGroup-Request iterator + * @param data [in] The contents of the WriteGroup-Request message + * @param change_list_index [in] The index of the current value in the change + * list + * @param change_list [in] The current value in the change list + */ +void bacnet_write_group_service_change_list_value_set( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list) +{ + BACNET_GROUP_CHANNEL_VALUE *value; + + value = bacnet_write_group_change_list_element(data, change_list_index); + if (value) { + (void)bacnet_group_channel_value_copy(value, change_list); + } +} + +/** + * @brief Decode the WriteGroup service request + * + * WriteGroup-Request ::= SEQUENCE { + * group-number [0] Unsigned32, + * write-priority [1] Unsigned (1..16), + * change-list [2] SEQUENCE OF BACnetGroupChannelValue, + * inhibit-delay [3] BOOLEAN OPTIONAL + * } + * + * @param apdu Pointer to the buffer for decoding. + * @param apdu_size Count of valid bytes in the buffer. + * @param data Pointer to the property decoded data to be stored + * + * @return Bytes decoded or BACNET_STATUS_ERROR on error. + */ +int bacnet_write_group_service_request_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_WRITE_GROUP_DATA *data) +{ + return bacnet_write_group_service_request_decode_iterate( + apdu, apdu_size, data, + bacnet_write_group_service_change_list_value_set); +} + +/** + * @brief Decode the WriteGroup-Request and call the WriteGroup handler + * function to process each change-list element of the request + * + * WriteGroup-Request ::= SEQUENCE { + * group-number [0] Unsigned32, + * write-priority [1] Unsigned (1..16), + * change-list [2] SEQUENCE OF BACnetGroupChannelValue ::= SEQUENCE { + * channel [0] Unsigned16, + * overriding-priority [1] Unsigned (1..16) OPTIONAL, + * value BACnetChannelValue + * } + * inhibit-delay [3] BOOLEAN OPTIONAL + * } + * + * @param apdu [in] Buffer of bytes received. + * @param apdu_size [in] Count of valid bytes in the buffer. + * @param callback [in] The function to call for each change-list element + * @return Bytes decoded or BACNET_STATUS_ERROR on error. + */ +int bacnet_write_group_service_request_decode_iterate( + const uint8_t *apdu, + size_t apdu_size, + BACNET_WRITE_GROUP_DATA *data, + BACnet_Write_Group_Callback callback) +{ + int len = 0; + int apdu_len = 0; + const uint8_t *change_list_apdu; + size_t change_list_apdu_size; + + /* group-number [0] Unsigned32 */ + len = write_group_service_group_number_decode( + &apdu[apdu_len], apdu_size - apdu_len, data); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + /* write-priority [1] Unsigned (1..16) */ + len = write_group_service_write_priority_decode( + &apdu[apdu_len], apdu_size - apdu_len, data); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + /* change-list [2] SEQUENCE OF BACnetGroupChannelValue */ + change_list_apdu = &apdu[apdu_len]; + change_list_apdu_size = apdu_size - apdu_len; + len = write_group_service_change_list_decode( + change_list_apdu, change_list_apdu_size, data, NULL); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + if (apdu_len < apdu_size) { + /* inhibit-delay [3] BOOLEAN OPTIONAL */ + len = write_group_service_inhibit_delay_decode( + &apdu[apdu_len], apdu_size - apdu_len, data); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + } else { + if (data) { + data->inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_NONE; + } + } + len = write_group_service_change_list_decode( + change_list_apdu, change_list_apdu_size, data, callback); + if (len <= 0) { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Copy WriteGroup data to another WriteGroup data + * @param dest Pointer to the destination data + * @param src Pointer to the source data + * @return true if the values are copied + */ +bool bacnet_write_group_copy( + BACNET_WRITE_GROUP_DATA *dest, const BACNET_WRITE_GROUP_DATA *src) +{ + const BACNET_GROUP_CHANNEL_VALUE *value_src; + BACNET_GROUP_CHANNEL_VALUE *value_dest; + + if (!dest || !src) { + return false; + } + dest->group_number = src->group_number; + dest->write_priority = src->write_priority; + dest->inhibit_delay = src->inhibit_delay; + value_src = &src->change_list; + value_dest = &dest->change_list; + while (value_src && value_dest) { + bacnet_group_channel_value_copy(value_dest, value_src); + value_src = value_src->next; + value_dest = value_dest->next; + } + if (value_src || value_dest) { + return false; + } + + return true; +} + +/** + * @brief Compare two WriteGroup service requests + * @param data1 Pointer to the first data to compare + * @param data2 Pointer to the second data to compare + * @return true if the values are the same, else false + */ +bool bacnet_write_group_same( + const BACNET_WRITE_GROUP_DATA *data1, const BACNET_WRITE_GROUP_DATA *data2) +{ + if (!data1 || !data2) { + return false; + } + if (data1->group_number != data2->group_number) { + return false; + } + if (data1->write_priority != data2->write_priority) { + return false; + } + if (data1->inhibit_delay != data2->inhibit_delay) { + return false; + } + + return bacnet_group_change_list_same( + &data1->change_list, &data2->change_list); +} + +/** + * @brief Compare two BACnetGroupChannelValue value lists + */ +bool bacnet_group_change_list_same( + const BACNET_GROUP_CHANNEL_VALUE *head1, + const BACNET_GROUP_CHANNEL_VALUE *head2) +{ + const BACNET_GROUP_CHANNEL_VALUE *data1; + const BACNET_GROUP_CHANNEL_VALUE *data2; + + data1 = head1; + data2 = head2; + while (data1 && data2) { + if (!bacnet_group_channel_value_same(data1, data2)) { + return false; + } + data1 = data1->next; + data2 = data2->next; + } + if (data1 || data2) { + return false; + } + + return true; +} + +/** + * @brief Compare two BACnetGroupChannelValue values + * @param value1 Pointer to the first value to compare + * @param value2 Pointer to the second value to compare + * @return true if the values are the same, else false + */ +bool bacnet_group_channel_value_same( + const BACNET_GROUP_CHANNEL_VALUE *value1, + const BACNET_GROUP_CHANNEL_VALUE *value2) +{ + if (!value1 || !value2) { + return false; + } + if (value1->channel != value2->channel) { + return false; + } + if (value1->overriding_priority != value2->overriding_priority) { + return false; + } + if (!bacnet_channel_value_same(&value1->value, &value2->value)) { + return false; + } + + return true; +} + +/** + * @brief Encode a list of BACnetGroupChannelValue values + * + * BACnetGroupChannelValue ::= SEQUENCE { + * channel [0] Unsigned16, + * overriding-priority [1] Unsigned (1..16) OPTIONAL, + * value BACnetChannelValue + * } + * + * @param apdu Pointer to the buffer for encoded values + * @param head Pointer to the first value in the list + * @return Bytes encoded or zero on error. + */ +int bacnet_group_channel_value_encode( + uint8_t *apdu, const BACNET_GROUP_CHANNEL_VALUE *head) +{ + const BACNET_GROUP_CHANNEL_VALUE *value; + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + value = head; + while (value) { + /* channel [0] Unsigned16 */ + len = encode_context_unsigned(apdu, 0, value->channel); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* overriding-priority [1] Unsigned (1..16) OPTIONAL */ + if ((value->overriding_priority >= BACNET_MIN_PRIORITY) && + (value->overriding_priority <= BACNET_MAX_PRIORITY)) { + len = encode_context_unsigned(apdu, 1, value->overriding_priority); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + /* value BACnetChannelValue */ + len = bacnet_channel_value_type_encode(apdu, &value->value); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* is there another one to encode? */ + value = value->next; + } + + return apdu_len; +} + +/** + * @brief Decode a list of BACnetGroupChannelValue values + * + * BACnetGroupChannelValue ::= SEQUENCE { + * channel [0] Unsigned16, + * overriding-priority [1] Unsigned (1..16) OPTIONAL, + * value BACnetChannelValue + * } + * + * @param apdu Pointer to the buffer for encoded values + * @param apdu_size Count of valid bytes in the buffer + * @param value Pointer to the first value in the list + * @return Bytes decoded or BACNET_STATUS_ERROR on error. + */ +int bacnet_group_channel_value_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_GROUP_CHANNEL_VALUE *value) +{ + int len = 0; + int apdu_len = 0; + BACNET_UNSIGNED_INTEGER unsigned_value; + BACNET_CHANNEL_VALUE channel_value; + + if (!apdu) { + return BACNET_STATUS_ERROR; + } + /* channel [0] Unsigned16 */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &unsigned_value); + if (len > 0) { + if (unsigned_value > UINT16_MAX) { + return BACNET_STATUS_ERROR; + } + if (value) { + value->channel = (uint16_t)unsigned_value; + } + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + /* overriding-priority [1] Unsigned (1..16) OPTIONAL */ + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value); + if (len > 0) { + if ((unsigned_value >= BACNET_MIN_PRIORITY) && + (unsigned_value <= BACNET_MAX_PRIORITY)) { + if (value) { + value->overriding_priority = (uint8_t)unsigned_value; + } + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + } else { + if (value) { + value->overriding_priority = BACNET_NO_PRIORITY; + } + } + /* value BACnetChannelValue */ + len = bacnet_channel_value_decode( + &apdu[apdu_len], apdu_size - apdu_len, &channel_value); + if (len > 0) { + if (value) { + memcpy(&value->value, &channel_value, sizeof(BACNET_CHANNEL_VALUE)); + } + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + + return apdu_len; +} + +/** + * @brief Copy BACnetGroupChannelValue data to another BACnetGroupChannelValue + * data + * @param dest Pointer to the destination data + * @param src Pointer to the source data + * @return true if values are able to be copied + */ +bool bacnet_group_channel_value_copy( + BACNET_GROUP_CHANNEL_VALUE *dest, const BACNET_GROUP_CHANNEL_VALUE *src) +{ + if (!dest || !src) { + return false; + } + dest->channel = src->channel; + dest->overriding_priority = src->overriding_priority; + + return bacnet_channel_value_copy(&dest->value, &src->value); +} + +/** + * @brief Count the number of BACnetGroupChannelValue elements in change-list + * @param data pointer to the WriteGroup data + * @return number of elements in the list + */ +unsigned bacnet_write_group_change_list_count(BACNET_WRITE_GROUP_DATA *data) +{ + BACNET_GROUP_CHANNEL_VALUE *value; + unsigned count = 0; + + if (!data) { + return 0; + } + value = &data->change_list; + while (value) { + count++; + value = value->next; + } + + return count; +} + +/** + * @brief Append a BACnetGroupChannelValue element to change-list + * @param data pointer to the WriteGroup data + * @param element pointer to an element to add to the list + * @param size number of elements in the array + * @return true if the element is added to the list + */ +bool bacnet_write_group_change_list_append( + BACNET_WRITE_GROUP_DATA *data, BACNET_GROUP_CHANNEL_VALUE *element) +{ + BACNET_GROUP_CHANNEL_VALUE *value; + + if (!data || !element) { + return false; + } + value = &data->change_list; + while (value) { + if (!value->next) { + value->next = element; + return true; + } + value = value->next; + } + + return false; +} + +/** + * @brief Add an array of BACnetGroupChannelValue to linked list + * @param array pointer to element zero of the array + * @param size number of elements in the array + * @return true if the array is added to the linked list + */ +bool bacnet_write_group_change_list_array_link( + BACNET_WRITE_GROUP_DATA *data, + BACNET_GROUP_CHANNEL_VALUE *array, + size_t size) +{ + size_t i = 0; + BACNET_GROUP_CHANNEL_VALUE *value; + + if (!data || !array || (size == 0)) { + return false; + } + value = &data->change_list; + for (i = 0; i < size; i++) { + value->next = &array[i]; + value = value->next; + } + + return true; +} + +/** + * @brief Get an array element of BACnetGroupChannelValue from WriteGroup data + * @param data pointer to the WriteGroup data + * @param index element number to retrieve 0..N + */ +BACNET_GROUP_CHANNEL_VALUE *bacnet_write_group_change_list_element( + BACNET_WRITE_GROUP_DATA *data, unsigned index) +{ + BACNET_GROUP_CHANNEL_VALUE *value; + unsigned i = 0; + + if (!data) { + return NULL; + } + value = &data->change_list; + while (i < index) { + if (value) { + value = value->next; + } + i++; + } + + return value; +} diff --git a/src/bacnet/write_group.h b/src/bacnet/write_group.h new file mode 100644 index 00000000..a3a2d8c7 --- /dev/null +++ b/src/bacnet/write_group.h @@ -0,0 +1,154 @@ +/** + * @file + * @brief API for BACnet WriteGroup service encoder and decoder + * @author Steve Karg + * @date 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_WRITE_GROUP_H +#define BACNET_WRITE_GROUP_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdcode.h" +#include "bacnet/bacapp.h" +#include "bacnet/channel_value.h" + +/** + * BACnetGroupChannelValue ::= SEQUENCE { + * channel [0] Unsigned16, + * overriding-priority [1] Unsigned (1..16) OPTIONAL, + * value BACnetChannelValue + * } + */ +struct BACnet_Group_Channel_Value; +typedef struct BACnet_Group_Channel_Value { + uint16_t channel; + uint8_t overriding_priority; + BACNET_CHANNEL_VALUE value; + struct BACnet_Group_Channel_Value *next; +} BACNET_GROUP_CHANNEL_VALUE; + +typedef enum { + WRITE_GROUP_INHIBIT_DELAY_NONE = 0, + WRITE_GROUP_INHIBIT_DELAY_TRUE = 1, + WRITE_GROUP_INHIBIT_DELAY_FALSE = 2 +} WRITE_GROUP_INHIBIT_DELAY; + +/** + * WriteGroup-Request ::= SEQUENCE { + * group-number [0] Unsigned32, + * write-priority [1] Unsigned (1..16), + * change-list [2] SEQUENCE OF BACnetGroupChannelValue, + * inhibit-delay [3] BOOLEAN OPTIONAL + * } + */ +struct BACnet_Write_Group_Data; +typedef struct BACnet_Write_Group_Data { + uint32_t group_number; + uint8_t write_priority; + /* simple linked list of values */ + BACNET_GROUP_CHANNEL_VALUE change_list; + WRITE_GROUP_INHIBIT_DELAY inhibit_delay; + struct BACnet_Write_Group_Data *next; +} BACNET_WRITE_GROUP_DATA; + +/** + * @brief generic callback for WriteGroup-Request iterator + * @param data [in] The contents of the WriteGroup-Request message + * @param change_list_index [in] The index of the current value in the change + * list + * @param change_list [in] The current value in the change list + */ +typedef void (*BACnet_Write_Group_Callback)( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list); +struct BACnet_Write_Group_Notification; +typedef struct BACnet_Write_Group_Notification { + struct BACnet_Write_Group_Notification *next; + BACnet_Write_Group_Callback callback; +} BACNET_WRITE_GROUP_NOTIFICATION; + +/** + * @brief Process a WriteGroup-Request message, one value at a time + * @param device_id [in] The device ID of the source of the message + * @param data [in] The contents of the WriteGroup-Request message + */ +typedef void (*write_group_request_process)( + uint32_t device_id, BACNET_WRITE_GROUP_DATA *data); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int bacnet_write_group_encode( + uint8_t *apdu, const BACNET_WRITE_GROUP_DATA *data); +BACNET_STACK_EXPORT +bool bacnet_write_group_same( + const BACNET_WRITE_GROUP_DATA *data1, const BACNET_WRITE_GROUP_DATA *data2); + +BACNET_STACK_EXPORT +size_t bacnet_write_group_service_request_encode( + uint8_t *apdu, size_t apdu_size, const BACNET_WRITE_GROUP_DATA *data); +BACNET_STACK_EXPORT +int bacnet_write_group_service_request_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_WRITE_GROUP_DATA *data); +BACNET_STACK_EXPORT +int bacnet_write_group_service_request_decode_iterate( + const uint8_t *apdu, + size_t apdu_size, + BACNET_WRITE_GROUP_DATA *data, + BACnet_Write_Group_Callback callback); + +BACNET_STACK_EXPORT +bool bacnet_write_group_copy( + BACNET_WRITE_GROUP_DATA *dest, const BACNET_WRITE_GROUP_DATA *src); + +BACNET_STACK_EXPORT +bool bacnet_group_change_list_same( + const BACNET_GROUP_CHANNEL_VALUE *head1, + const BACNET_GROUP_CHANNEL_VALUE *head2); +BACNET_STACK_EXPORT +unsigned bacnet_write_group_change_list_count(BACNET_WRITE_GROUP_DATA *data); +BACNET_STACK_EXPORT +bool bacnet_write_group_change_list_append( + BACNET_WRITE_GROUP_DATA *data, BACNET_GROUP_CHANNEL_VALUE *element); +BACNET_STACK_EXPORT +bool bacnet_write_group_change_list_array_link( + BACNET_WRITE_GROUP_DATA *data, + BACNET_GROUP_CHANNEL_VALUE *array, + size_t size); +BACNET_STACK_EXPORT +BACNET_GROUP_CHANNEL_VALUE *bacnet_write_group_change_list_element( + BACNET_WRITE_GROUP_DATA *data, unsigned index); + +BACNET_STACK_EXPORT +void bacnet_write_group_channel_value_process( + uint8_t *apdu, + size_t apdu_len, + BACNET_WRITE_GROUP_DATA *data, + write_group_request_process callback); + +BACNET_STACK_EXPORT +int bacnet_group_channel_value_encode( + uint8_t *apdu, const BACNET_GROUP_CHANNEL_VALUE *value); +BACNET_STACK_EXPORT +int bacnet_group_channel_value_decode( + const uint8_t *apdu, size_t apdu_size, BACNET_GROUP_CHANNEL_VALUE *value); +BACNET_STACK_EXPORT +bool bacnet_group_channel_value_same( + const BACNET_GROUP_CHANNEL_VALUE *value1, + const BACNET_GROUP_CHANNEL_VALUE *value2); +BACNET_STACK_EXPORT +bool bacnet_group_channel_value_copy( + BACNET_GROUP_CHANNEL_VALUE *dest, const BACNET_GROUP_CHANNEL_VALUE *src); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc51b4d1..c895f37a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -85,6 +85,7 @@ list(APPEND testdirs bacnet/bacreal bacnet/bacstr bacnet/bactimevalue + bacnet/channel_value bacnet/cov bacnet/create_object bacnet/datetime @@ -111,11 +112,12 @@ list(APPEND testdirs bacnet/specialevent bacnet/timestamp bacnet/timesync + bacnet/weeklyschedule bacnet/whohas bacnet/whois bacnet/wp bacnet/wpm - bacnet/weeklyschedule + bacnet/write_group ) # bacnet/basic/* diff --git a/test/bacnet/access_rule/CMakeLists.txt b/test/bacnet/access_rule/CMakeLists.txt index 550f185c..90635857 100644 --- a/test/bacnet/access_rule/CMakeLists.txt +++ b/test/bacnet/access_rule/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/bacapp/CMakeLists.txt b/test/bacnet/bacapp/CMakeLists.txt index cca5ff05..160b6add 100644 --- a/test/bacnet/bacapp/CMakeLists.txt +++ b/test/bacnet/bacapp/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/bacdest/CMakeLists.txt b/test/bacnet/bacdest/CMakeLists.txt index 743be862..74ca6b06 100644 --- a/test/bacnet/bacdest/CMakeLists.txt +++ b/test/bacnet/bacdest/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/bacdevobjpropref/CMakeLists.txt b/test/bacnet/bacdevobjpropref/CMakeLists.txt index b173eab0..f0592ef4 100644 --- a/test/bacnet/bacdevobjpropref/CMakeLists.txt +++ b/test/bacnet/bacdevobjpropref/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/bactimevalue/CMakeLists.txt b/test/bacnet/bactimevalue/CMakeLists.txt index 7735a22b..d1a4be5e 100644 --- a/test/bacnet/bactimevalue/CMakeLists.txt +++ b/test/bacnet/bactimevalue/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/binding/address/CMakeLists.txt b/test/bacnet/basic/binding/address/CMakeLists.txt index 82a35849..baf34ef7 100644 --- a/test/bacnet/basic/binding/address/CMakeLists.txt +++ b/test/bacnet/basic/binding/address/CMakeLists.txt @@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/acc/CMakeLists.txt b/test/bacnet/basic/object/acc/CMakeLists.txt index feccf5e2..d884f21e 100644 --- a/test/bacnet/basic/object/acc/CMakeLists.txt +++ b/test/bacnet/basic/object/acc/CMakeLists.txt @@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/access_credential/CMakeLists.txt b/test/bacnet/basic/object/access_credential/CMakeLists.txt index 02a8587e..2c80ee11 100644 --- a/test/bacnet/basic/object/access_credential/CMakeLists.txt +++ b/test/bacnet/basic/object/access_credential/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/access_door/CMakeLists.txt b/test/bacnet/basic/object/access_door/CMakeLists.txt index 8f6029cd..3661ab21 100644 --- a/test/bacnet/basic/object/access_door/CMakeLists.txt +++ b/test/bacnet/basic/object/access_door/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/access_point/CMakeLists.txt b/test/bacnet/basic/object/access_point/CMakeLists.txt index 698ff19b..464a828e 100644 --- a/test/bacnet/basic/object/access_point/CMakeLists.txt +++ b/test/bacnet/basic/object/access_point/CMakeLists.txt @@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/access_rights/CMakeLists.txt b/test/bacnet/basic/object/access_rights/CMakeLists.txt index 2d002fd0..b46450a7 100644 --- a/test/bacnet/basic/object/access_rights/CMakeLists.txt +++ b/test/bacnet/basic/object/access_rights/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/access_user/CMakeLists.txt b/test/bacnet/basic/object/access_user/CMakeLists.txt index 41f4a94c..1be1197d 100644 --- a/test/bacnet/basic/object/access_user/CMakeLists.txt +++ b/test/bacnet/basic/object/access_user/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/access_zone/CMakeLists.txt b/test/bacnet/basic/object/access_zone/CMakeLists.txt index b635e248..2a5e76b0 100644 --- a/test/bacnet/basic/object/access_zone/CMakeLists.txt +++ b/test/bacnet/basic/object/access_zone/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/ai/CMakeLists.txt b/test/bacnet/basic/object/ai/CMakeLists.txt index 2d5c2087..0a377049 100644 --- a/test/bacnet/basic/object/ai/CMakeLists.txt +++ b/test/bacnet/basic/object/ai/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/ao/CMakeLists.txt b/test/bacnet/basic/object/ao/CMakeLists.txt index 4b5b4ec6..eacb151e 100644 --- a/test/bacnet/basic/object/ao/CMakeLists.txt +++ b/test/bacnet/basic/object/ao/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/keylist.c diff --git a/test/bacnet/basic/object/av/CMakeLists.txt b/test/bacnet/basic/object/av/CMakeLists.txt index 029c9871..1dcb4015 100644 --- a/test/bacnet/basic/object/av/CMakeLists.txt +++ b/test/bacnet/basic/object/av/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/bacfile/CMakeLists.txt b/test/bacnet/basic/object/bacfile/CMakeLists.txt index 4495d085..be9a0a14 100644 --- a/test/bacnet/basic/object/bacfile/CMakeLists.txt +++ b/test/bacnet/basic/object/bacfile/CMakeLists.txt @@ -65,6 +65,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/apdu_mock.c diff --git a/test/bacnet/basic/object/bi/CMakeLists.txt b/test/bacnet/basic/object/bi/CMakeLists.txt index f3adebed..6a59ecf1 100644 --- a/test/bacnet/basic/object/bi/CMakeLists.txt +++ b/test/bacnet/basic/object/bi/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt index a714d87a..bd88a8b9 100644 --- a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt +++ b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/blo/CMakeLists.txt b/test/bacnet/basic/object/blo/CMakeLists.txt index bdd259b4..57b3927b 100644 --- a/test/bacnet/basic/object/blo/CMakeLists.txt +++ b/test/bacnet/basic/object/blo/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c diff --git a/test/bacnet/basic/object/bo/CMakeLists.txt b/test/bacnet/basic/object/bo/CMakeLists.txt index 0f278147..f47a324a 100644 --- a/test/bacnet/basic/object/bo/CMakeLists.txt +++ b/test/bacnet/basic/object/bo/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/bv/CMakeLists.txt b/test/bacnet/basic/object/bv/CMakeLists.txt index 5a8037f5..b35e3650 100644 --- a/test/bacnet/basic/object/bv/CMakeLists.txt +++ b/test/bacnet/basic/object/bv/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/calendar/CMakeLists.txt b/test/bacnet/basic/object/calendar/CMakeLists.txt index 32c8ac1e..94961e4d 100644 --- a/test/bacnet/basic/object/calendar/CMakeLists.txt +++ b/test/bacnet/basic/object/calendar/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/weeklyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/bactimevalue.c ${SRC_DIR}/bacnet/dailyschedule.c # Test and test library files diff --git a/test/bacnet/basic/object/channel/CMakeLists.txt b/test/bacnet/basic/object/channel/CMakeLists.txt index 6d90b440..725e1ded 100644 --- a/test/bacnet/basic/object/channel/CMakeLists.txt +++ b/test/bacnet/basic/object/channel/CMakeLists.txt @@ -27,6 +27,7 @@ add_compile_definitions( include_directories( ${SRC_DIR} + ${TST_DIR}/bacnet/basic/object/test ${TST_DIR}/ztest/include ) @@ -48,19 +49,24 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/bactimevalue.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c ${SRC_DIR}/bacnet/datetime.c ${SRC_DIR}/bacnet/indtext.c ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/property.c + ${SRC_DIR}/bacnet/proplist.c ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c ${SRC_DIR}/bacnet/weeklyschedule.c ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c + ${TST_DIR}/bacnet/basic/object/test/property_test.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c ${ZTST_DIR}/ztest_mock.c ${ZTST_DIR}/ztest.c diff --git a/test/bacnet/basic/object/channel/src/main.c b/test/bacnet/basic/object/channel/src/main.c index e4f72870..7068e37a 100644 --- a/test/bacnet/basic/object/channel/src/main.c +++ b/test/bacnet/basic/object/channel/src/main.c @@ -10,6 +10,7 @@ #include #include #include +#include /** * @addtogroup bacnet_tests @@ -19,122 +20,91 @@ /** * @brief Test */ -static void test_Channel_ReadProperty(void) +static void test_Channel_Property_Read_Write(void) { - uint8_t apdu[MAX_APDU] = { 0 }; - int len = 0; - int test_len = 0; - BACNET_READ_PROPERTY_DATA rpdata = { 0 }; - BACNET_APPLICATION_DATA_VALUE value = { 0 }; - BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; const uint32_t instance = 123; - const int *pRequired = NULL; - const int *pOptional = NULL; - const int *pProprietary = NULL; unsigned count = 0; - bool status = false; - unsigned index; + unsigned index = 0; + const char *sample_name = "Channel:0"; const char *test_name = NULL; - char *sample_name = "sample"; + uint32_t test_instance = 0; + bool status = false; + const int skip_fail_property_list[] = { -1 }; + BACNET_CHANNEL_VALUE channel_value = { 0 }; + BACNET_WRITE_PROPERTY_DATA wp_data = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 }; Channel_Init(); Channel_Create(instance); status = Channel_Valid_Instance(instance); zassert_true(status, NULL); + status = Channel_Valid_Instance(instance - 1); + zassert_false(status, NULL); index = Channel_Instance_To_Index(instance); zassert_equal(index, 0, NULL); - + test_instance = Channel_Index_To_Instance(index); + zassert_equal(instance, test_instance, NULL); count = Channel_Count(); zassert_true(count > 0, NULL); - rpdata.application_data = &apdu[0]; - rpdata.application_data_len = sizeof(apdu); - rpdata.object_type = OBJECT_CHANNEL; - rpdata.object_instance = Channel_Index_To_Instance(0); - status = Channel_Valid_Instance(rpdata.object_instance); + /* configure the instance property values and test API for lists */ + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_ANALOG_OUTPUT; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + status = + Channel_Reference_List_Member_Element_Set(instance, index, &member); zassert_true(status, NULL); - Channel_Property_Lists(&pRequired, &pOptional, &pProprietary); - while ((*pRequired) != -1) { - rpdata.object_property = *pRequired; - rpdata.array_index = BACNET_ARRAY_ALL; - len = Channel_Read_Property(&rpdata); - zassert_not_equal( - len, BACNET_STATUS_ERROR, - "property '%s': failed to ReadProperty!\n", - bactext_property_name(rpdata.object_property)); - if (len > 0) { - test_len = bacapp_decode_application_data( - rpdata.application_data, (uint8_t)rpdata.application_data_len, - &value); - if ((rpdata.object_property == PROP_PRIORITY_ARRAY) || - (rpdata.object_property == PROP_CONTROL_GROUPS) || - (rpdata.object_property == - PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES)) { - /* FIXME: known fail to decode */ - len = test_len; - } - zassert_equal( - len, test_len, "property '%s': failed to decode!\n", - bactext_property_name(rpdata.object_property)); - /* check WriteProperty properties */ - wpdata.object_type = rpdata.object_type; - wpdata.object_instance = rpdata.object_instance; - wpdata.object_property = rpdata.object_property; - wpdata.array_index = rpdata.array_index; - memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); - wpdata.application_data_len = len; - wpdata.error_code = ERROR_CODE_SUCCESS; - status = Channel_Write_Property(&wpdata); - if (!status) { - /* verify WriteProperty property is known */ - zassert_not_equal( - wpdata.error_code, ERROR_CODE_UNKNOWN_PROPERTY, - "property '%s': WriteProperty Unknown!\n", - bactext_property_name(rpdata.object_property)); - } - } - pRequired++; - } - while ((*pOptional) != -1) { - rpdata.object_property = *pOptional; - rpdata.array_index = BACNET_ARRAY_ALL; - len = Channel_Read_Property(&rpdata); - zassert_not_equal( - len, BACNET_STATUS_ERROR, - "property '%s': failed to ReadProperty!\n", - bactext_property_name(rpdata.object_property)); - if (len > 0) { - test_len = bacapp_decode_application_data( - rpdata.application_data, (uint8_t)rpdata.application_data_len, - &value); - zassert_equal( - len, test_len, "property '%s': failed to decode!\n", - bactext_property_name(rpdata.object_property)); - /* check WriteProperty properties */ - wpdata.object_type = rpdata.object_type; - wpdata.object_instance = rpdata.object_instance; - wpdata.object_property = rpdata.object_property; - wpdata.array_index = rpdata.array_index; - memcpy(&wpdata.application_data, rpdata.application_data, MAX_APDU); - wpdata.application_data_len = len; - wpdata.error_code = ERROR_CODE_SUCCESS; - status = Channel_Write_Property(&wpdata); - if (!status) { - /* verify WriteProperty property is known */ - zassert_not_equal( - wpdata.error_code, ERROR_CODE_UNKNOWN_PROPERTY, - "property '%s': WriteProperty Unknown!\n", - bactext_property_name(rpdata.object_property)); - } - } - pOptional++; - } - /* check for unsupported property - use ALL */ - rpdata.object_property = PROP_ALL; - len = Channel_Read_Property(&rpdata); - zassert_equal(len, BACNET_STATUS_ERROR, NULL); - wpdata.object_property = PROP_ALL; - status = Channel_Write_Property(&wpdata); - zassert_false(status, NULL); + status = Channel_Control_Groups_Element_Set(instance, 1, 1); + zassert_true(status, NULL); + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_BINARY_OUTPUT; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_MULTI_STATE_OUTPUT; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_LIGHTING_OUTPUT; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_COLOR; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + member.deviceIdentifier.type = OBJECT_DEVICE; + member.deviceIdentifier.instance = 0; + member.objectIdentifier.type = OBJECT_COLOR_TEMPERATURE; + member.objectIdentifier.instance = 1; + member.propertyIdentifier = PROP_PRESENT_VALUE; + member.arrayIndex = BACNET_ARRAY_ALL; + index = Channel_Reference_List_Member_Element_Add(instance, &member); + zassert_not_equal(index, 0, NULL); + /* perform a general test for RP/WP */ + bacnet_object_properties_read_write_test( + OBJECT_CHANNEL, instance, Channel_Property_Lists, Channel_Read_Property, + Channel_Write_Property, skip_fail_property_list); /* test the ASCII name get/set */ status = Channel_Name_Set(instance, sample_name); zassert_true(status, NULL); @@ -144,9 +114,131 @@ static void test_Channel_ReadProperty(void) zassert_true(status, NULL); test_name = Channel_Name_ASCII(instance); zassert_equal(test_name, NULL, NULL); + + /* test specific WriteProperty values - common configuration */ + wp_data.object_type = OBJECT_CHANNEL; + wp_data.object_instance = instance; + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_MAX_PRIORITY; + /* specific WriteProperty value */ + wp_data.object_property = PROP_PRESENT_VALUE; + value.tag = BACNET_APPLICATION_TAG_CHANNEL_VALUE; + value.type.Channel_Value.tag = BACNET_APPLICATION_TAG_REAL; + value.type.Channel_Value.type.Real = 3.14159; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + /* specific WriteProperty value */ + wp_data.object_property = PROP_OUT_OF_SERVICE; + value.tag = BACNET_APPLICATION_TAG_BOOLEAN; + value.type.Boolean = true; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + /* specific WriteProperty value */ + wp_data.object_property = PROP_CHANNEL_NUMBER; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = 123; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + value.type.Unsigned_Int = UINT16_MAX + 1; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* specific WriteProperty value */ + wp_data.object_property = PROP_CONTROL_GROUPS; + wp_data.array_index = 1; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + /* min valid value */ + value.type.Unsigned_Int = 0; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + /* max valid value */ + value.type.Unsigned_Int = UINT16_MAX; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + /* array size - read-only */ + wp_data.array_index = 0; + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* out-of-range value */ + wp_data.array_index = 1; + value.type.Unsigned_Int = UINT16_MAX + 1; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* invalid data type for Array element */ + wp_data.array_index = 1; + value.type.Real = 3.14159f; + value.tag = BACNET_APPLICATION_TAG_REAL; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* invalid data type for Array size */ + wp_data.array_index = 0; + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* invalid array-index - probably */ + wp_data.array_index = BACNET_ARRAY_ALL - 1; + value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value.type.Unsigned_Int = 0; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* specific WriteProperty value */ + wp_data.array_index = 1; + wp_data.priority = BACNET_MAX_PRIORITY; + wp_data.object_property = PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES; + value.tag = BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE; + value.type.Device_Object_Property_Reference.objectIdentifier.type = + OBJECT_ANALOG_OUTPUT; + value.type.Device_Object_Property_Reference.objectIdentifier.instance = 1; + value.type.Device_Object_Property_Reference.propertyIdentifier = + PROP_PRESENT_VALUE; + value.type.Device_Object_Property_Reference.arrayIndex = BACNET_ARRAY_ALL; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_true(status, NULL); + wp_data.array_index = 0; + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + wp_data.array_index = BACNET_ARRAY_ALL - 1; + status = Channel_Write_Property(&wp_data); + zassert_false(status, NULL); + /* read-only property */ + wp_data.array_index = BACNET_ARRAY_ALL; + wp_data.priority = BACNET_MAX_PRIORITY; + wp_data.object_property = PROP_OBJECT_TYPE; + value.tag = BACNET_APPLICATION_TAG_ENUMERATED; + value.type.Enumerated = OBJECT_ANALOG_INPUT; + wp_data.application_data_len = + bacapp_encode_application_data(wp_data.application_data, &value); + status = Channel_Write_Property(&wp_data); + zassert_equal(wp_data.error_class, ERROR_CLASS_PROPERTY, NULL); + zassert_equal(wp_data.error_code, ERROR_CODE_WRITE_ACCESS_DENIED, NULL); + zassert_false(status, NULL); + /* present-value API */ + channel_value.tag = BACNET_APPLICATION_TAG_REAL; + channel_value.type.Real = 3.14159f; + status = Channel_Present_Value_Set(instance, 1, &channel_value); + zassert_true(status, NULL); /* cleanup */ status = Channel_Delete(instance); zassert_true(status, NULL); + Channel_Cleanup(); } /** * @} @@ -154,7 +246,8 @@ static void test_Channel_ReadProperty(void) void test_main(void) { - ztest_test_suite(channel_tests, ztest_unit_test(test_Channel_ReadProperty)); + ztest_test_suite( + channel_tests, ztest_unit_test(test_Channel_Property_Read_Write)); ztest_run_test_suite(channel_tests); } diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt index 82ff86da..50839e1e 100644 --- a/test/bacnet/basic/object/color_object/CMakeLists.txt +++ b/test/bacnet/basic/object/color_object/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c diff --git a/test/bacnet/basic/object/color_temperature/CMakeLists.txt b/test/bacnet/basic/object/color_temperature/CMakeLists.txt index 80d33638..c0c4dc31 100644 --- a/test/bacnet/basic/object/color_temperature/CMakeLists.txt +++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c diff --git a/test/bacnet/basic/object/command/CMakeLists.txt b/test/bacnet/basic/object/command/CMakeLists.txt index 65a4fb20..91fa93ec 100644 --- a/test/bacnet/basic/object/command/CMakeLists.txt +++ b/test/bacnet/basic/object/command/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/credential_data_input/CMakeLists.txt b/test/bacnet/basic/object/credential_data_input/CMakeLists.txt index 0912608b..0f2a212e 100644 --- a/test/bacnet/basic/object/credential_data_input/CMakeLists.txt +++ b/test/bacnet/basic/object/credential_data_input/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/csv/CMakeLists.txt b/test/bacnet/basic/object/csv/CMakeLists.txt index bcdaa097..a84d5f7b 100644 --- a/test/bacnet/basic/object/csv/CMakeLists.txt +++ b/test/bacnet/basic/object/csv/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/memcopy.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index a92bf8d8..a451772b 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -106,6 +106,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ./stubs.c # Test and test library files ./src/main.c diff --git a/test/bacnet/basic/object/iv/CMakeLists.txt b/test/bacnet/basic/object/iv/CMakeLists.txt index 04d177e2..c2303dbc 100644 --- a/test/bacnet/basic/object/iv/CMakeLists.txt +++ b/test/bacnet/basic/object/iv/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/lc/CMakeLists.txt b/test/bacnet/basic/object/lc/CMakeLists.txt index 19b0b381..def47599 100644 --- a/test/bacnet/basic/object/lc/CMakeLists.txt +++ b/test/bacnet/basic/object/lc/CMakeLists.txt @@ -69,6 +69,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/datetime_local.c diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt index e33e259b..cebbe47d 100644 --- a/test/bacnet/basic/object/lo/CMakeLists.txt +++ b/test/bacnet/basic/object/lo/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c diff --git a/test/bacnet/basic/object/lsp/CMakeLists.txt b/test/bacnet/basic/object/lsp/CMakeLists.txt index cbb2c62c..614bf8b8 100644 --- a/test/bacnet/basic/object/lsp/CMakeLists.txt +++ b/test/bacnet/basic/object/lsp/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/lsz/CMakeLists.txt b/test/bacnet/basic/object/lsz/CMakeLists.txt index 08b31d1e..b00cdd25 100644 --- a/test/bacnet/basic/object/lsz/CMakeLists.txt +++ b/test/bacnet/basic/object/lsz/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/ms-input/CMakeLists.txt b/test/bacnet/basic/object/ms-input/CMakeLists.txt index 881f43bb..3213b829 100644 --- a/test/bacnet/basic/object/ms-input/CMakeLists.txt +++ b/test/bacnet/basic/object/ms-input/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/mso/CMakeLists.txt b/test/bacnet/basic/object/mso/CMakeLists.txt index f360f78c..a5ee4440 100644 --- a/test/bacnet/basic/object/mso/CMakeLists.txt +++ b/test/bacnet/basic/object/mso/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/msv/CMakeLists.txt b/test/bacnet/basic/object/msv/CMakeLists.txt index ed955d22..76175ecf 100644 --- a/test/bacnet/basic/object/msv/CMakeLists.txt +++ b/test/bacnet/basic/object/msv/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/days.c ${SRC_DIR}/bacnet/basic/sys/debug.c diff --git a/test/bacnet/basic/object/nc/CMakeLists.txt b/test/bacnet/basic/object/nc/CMakeLists.txt index 18860255..57df9fc6 100644 --- a/test/bacnet/basic/object/nc/CMakeLists.txt +++ b/test/bacnet/basic/object/nc/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./stubs.c ./src/main.c diff --git a/test/bacnet/basic/object/netport/CMakeLists.txt b/test/bacnet/basic/object/netport/CMakeLists.txt index 86279d35..78fd8707 100644 --- a/test/bacnet/basic/object/netport/CMakeLists.txt +++ b/test/bacnet/basic/object/netport/CMakeLists.txt @@ -55,6 +55,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/lighting.c ${SRC_DIR}/bacnet/proplist.c + ${SRC_DIR}/bacnet/property.c ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/wp.c ${SRC_DIR}/bacnet/weeklyschedule.c @@ -62,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/osv/CMakeLists.txt b/test/bacnet/basic/object/osv/CMakeLists.txt index a8f727d7..77b3982b 100644 --- a/test/bacnet/basic/object/osv/CMakeLists.txt +++ b/test/bacnet/basic/object/osv/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/piv/CMakeLists.txt b/test/bacnet/basic/object/piv/CMakeLists.txt index c54b4677..dadc50f7 100644 --- a/test/bacnet/basic/object/piv/CMakeLists.txt +++ b/test/bacnet/basic/object/piv/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/basic/object/schedule/CMakeLists.txt b/test/bacnet/basic/object/schedule/CMakeLists.txt index 4a031d3d..8f014795 100644 --- a/test/bacnet/basic/object/schedule/CMakeLists.txt +++ b/test/bacnet/basic/object/schedule/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/property_test.c diff --git a/test/bacnet/basic/object/structured_view/CMakeLists.txt b/test/bacnet/basic/object/structured_view/CMakeLists.txt index a61bf2bb..3c818261 100644 --- a/test/bacnet/basic/object/structured_view/CMakeLists.txt +++ b/test/bacnet/basic/object/structured_view/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c ${SRC_DIR}/bacnet/basic/sys/bigend.c ${SRC_DIR}/bacnet/basic/sys/debug.c ${SRC_DIR}/bacnet/basic/sys/keylist.c diff --git a/test/bacnet/basic/object/time_value/CMakeLists.txt b/test/bacnet/basic/object/time_value/CMakeLists.txt index 00610b9b..a9ef0ae0 100644 --- a/test/bacnet/basic/object/time_value/CMakeLists.txt +++ b/test/bacnet/basic/object/time_value/CMakeLists.txt @@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/datetime_local.c diff --git a/test/bacnet/basic/object/trendlog/CMakeLists.txt b/test/bacnet/basic/object/trendlog/CMakeLists.txt index 7eef6e40..6900778c 100644 --- a/test/bacnet/basic/object/trendlog/CMakeLists.txt +++ b/test/bacnet/basic/object/trendlog/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${TST_DIR}/bacnet/basic/object/test/device_mock.c diff --git a/test/bacnet/channel_value/CMakeLists.txt b/test/bacnet/channel_value/CMakeLists.txt new file mode 100644 index 00000000..fc71c7ef --- /dev/null +++ b/test/bacnet/channel_value/CMakeLists.txt @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + BACAPP_ALL + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/channel_value.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/lighting.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/channel_value/src/main.c b/test/bacnet/channel_value/src/main.c new file mode 100644 index 00000000..b9ef0b3a --- /dev/null +++ b/test/bacnet/channel_value/src/main.c @@ -0,0 +1,147 @@ +/** + * @file + * @brief Unit test for BACnetChannelValue. + * @author Steve Karg + * @date October 2024 + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include +#include +#include "bacnet/bactext.h" +#include "bacnet/channel_value.h" + +/** + * @addtogroup bacnet_tests + * @{ + */ + +/** @brief try decoding a real sample from Siemens, captured with wireshark */ +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(BACnetChannelValue_Tests, test_BACnetChannelValue) +#else +static void test_BACnetChannelValue(void) +#endif +{ + uint8_t apdu[MAX_APDU]; + int apdu_len, test_len, null_len; + bool status; + BACNET_CHANNEL_VALUE *value; + BACNET_CHANNEL_VALUE case_value[] = { + { .tag = BACNET_APPLICATION_TAG_NULL, .type.Real = 0.0f, .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_BOOLEAN, + .type.Boolean = false, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_UNSIGNED_INT, + .type.Unsigned_Int = 0xDEADBEEF, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_SIGNED_INT, + .type.Signed_Int = 0x00C0FFEE, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_REAL, + .type.Real = 3.141592654f, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_DOUBLE, + .type.Double = 2.32323232323, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_ENUMERATED, + .type.Enumerated = 0x0BADF00D, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND, + .type.Lighting_Command.operation = BACNET_LIGHTS_NONE, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_COLOR_COMMAND, + .type.Color_Command.operation = BACNET_COLOR_OPERATION_NONE, + .next = NULL }, + { .tag = BACNET_APPLICATION_TAG_XY_COLOR, + .type.XY_Color.x_coordinate = 0.0f, + .type.XY_Color.y_coordinate = 0.0f, + .next = NULL }, + }; + struct ascii_channel_value { + const char *string; + BACNET_APPLICATION_TAG tag; + } ascii_values[] = { + { "NULL", BACNET_APPLICATION_TAG_NULL }, + { "FALSE", BACNET_APPLICATION_TAG_BOOLEAN }, + { "1234567890", BACNET_APPLICATION_TAG_UNSIGNED_INT }, + { "-1234567890", BACNET_APPLICATION_TAG_SIGNED_INT }, + { "3.141592654", BACNET_APPLICATION_TAG_REAL }, + { "-3.141592654", BACNET_APPLICATION_TAG_REAL }, + { "F1.21", BACNET_APPLICATION_TAG_REAL }, + { "f1.21", BACNET_APPLICATION_TAG_REAL }, + { "D1.21", BACNET_APPLICATION_TAG_DOUBLE }, + { "d1.21", BACNET_APPLICATION_TAG_DOUBLE }, + { "L0", BACNET_APPLICATION_TAG_LIGHTING_COMMAND }, + { "l0", BACNET_APPLICATION_TAG_LIGHTING_COMMAND }, + { "C0", BACNET_APPLICATION_TAG_COLOR_COMMAND }, + { "c0", BACNET_APPLICATION_TAG_COLOR_COMMAND }, + { "X0.0,0.0", BACNET_APPLICATION_TAG_XY_COLOR }, + { "x0.0,0.0", BACNET_APPLICATION_TAG_XY_COLOR }, + }; + size_t i; + BACNET_CHANNEL_VALUE test_value = { 0 }; + + bacnet_channel_value_link_array(case_value, ARRAY_SIZE(case_value)); + value = &case_value[0]; + while (value) { + null_len = bacnet_channel_value_encode(NULL, sizeof(apdu), value); + if (value->tag != BACNET_APPLICATION_TAG_NULL) { + zassert_not_equal(null_len, 0, NULL); + } + apdu_len = bacnet_channel_value_encode(apdu, sizeof(apdu), value); + zassert_equal( + apdu_len, null_len, "value->tag: %s len=%d null_len=%d", + bactext_application_tag_name(value->tag), apdu_len, null_len); + test_len = bacnet_channel_value_decode(apdu, apdu_len, &test_value); + zassert_not_equal( + test_len, BACNET_STATUS_ERROR, "value->tag: %s test_len=%d", + bactext_application_tag_name(value->tag), test_len); + zassert_equal(test_len, apdu_len, NULL); + zassert_equal( + value->tag, test_value.tag, "value->tag: %s test_tag=%s", + bactext_application_tag_name(value->tag), + bactext_application_tag_name(test_value.tag)); + status = bacnet_channel_value_same(value, &test_value); + zassert_true( + status, "decode: different: %s", + bactext_application_tag_name(value->tag)); + status = bacnet_channel_value_copy(&test_value, value); + zassert_true( + status, "copy: failed: %s", + bactext_application_tag_name(value->tag)); + status = bacnet_channel_value_same(value, &test_value); + zassert_true( + status, "copy: different: %s", + bactext_application_tag_name(value->tag)); + value = value->next; + } + for (i = 0; i < ARRAY_SIZE(ascii_values); i++) { + status = bacnet_channel_value_from_ascii( + &test_value, ascii_values[i].string); + zassert_true(status, "from_ascii: failed: %s", ascii_values[i].string); + zassert_equal( + test_value.tag, ascii_values[i].tag, "from_ascii: %s", + ascii_values[i].string); + } +} + +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(BACnetChannelValue_Tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite( + BACnetChannelValue_Tests, ztest_unit_test(test_BACnetChannelValue)); + + ztest_run_test_suite(BACnetChannelValue_Tests); +} +#endif diff --git a/test/bacnet/cov/CMakeLists.txt b/test/bacnet/cov/CMakeLists.txt index eba71cae..d8b083fc 100644 --- a/test/bacnet/cov/CMakeLists.txt +++ b/test/bacnet/cov/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt index b32bdd48..939027b2 100644 --- a/test/bacnet/create_object/CMakeLists.txt +++ b/test/bacnet/create_object/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/delete_object/CMakeLists.txt b/test/bacnet/delete_object/CMakeLists.txt index 6436e76e..83b2ae48 100644 --- a/test/bacnet/delete_object/CMakeLists.txt +++ b/test/bacnet/delete_object/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/event/CMakeLists.txt b/test/bacnet/event/CMakeLists.txt index f40e1178..2ba2e747 100644 --- a/test/bacnet/event/CMakeLists.txt +++ b/test/bacnet/event/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/hostnport.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/getalarm/CMakeLists.txt b/test/bacnet/getalarm/CMakeLists.txt index 0158f61e..6d0bb26a 100644 --- a/test/bacnet/getalarm/CMakeLists.txt +++ b/test/bacnet/getalarm/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/getevent/CMakeLists.txt b/test/bacnet/getevent/CMakeLists.txt index 4ea1b5ea..f2038a65 100644 --- a/test/bacnet/getevent/CMakeLists.txt +++ b/test/bacnet/getevent/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/hostnport/CMakeLists.txt b/test/bacnet/hostnport/CMakeLists.txt index 5b54b062..510084e1 100644 --- a/test/bacnet/hostnport/CMakeLists.txt +++ b/test/bacnet/hostnport/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/list_element/CMakeLists.txt b/test/bacnet/list_element/CMakeLists.txt index b23d7e01..e650161a 100644 --- a/test/bacnet/list_element/CMakeLists.txt +++ b/test/bacnet/list_element/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/lso/CMakeLists.txt b/test/bacnet/lso/CMakeLists.txt index 7d14db38..dd3fbf76 100644 --- a/test/bacnet/lso/CMakeLists.txt +++ b/test/bacnet/lso/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/ptransfer/CMakeLists.txt b/test/bacnet/ptransfer/CMakeLists.txt index 55203763..4a4f64f3 100644 --- a/test/bacnet/ptransfer/CMakeLists.txt +++ b/test/bacnet/ptransfer/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/rpm/CMakeLists.txt b/test/bacnet/rpm/CMakeLists.txt index b8d6a650..d753ee0c 100644 --- a/test/bacnet/rpm/CMakeLists.txt +++ b/test/bacnet/rpm/CMakeLists.txt @@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/specialevent/CMakeLists.txt b/test/bacnet/specialevent/CMakeLists.txt index 3055a50b..a9f858c3 100644 --- a/test/bacnet/specialevent/CMakeLists.txt +++ b/test/bacnet/specialevent/CMakeLists.txt @@ -57,7 +57,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/weeklyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c - ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/timesync/CMakeLists.txt b/test/bacnet/timesync/CMakeLists.txt index 7c243172..ae1df5f5 100644 --- a/test/bacnet/timesync/CMakeLists.txt +++ b/test/bacnet/timesync/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/weeklyschedule/CMakeLists.txt b/test/bacnet/weeklyschedule/CMakeLists.txt index c1110bc5..b5fb1100 100644 --- a/test/bacnet/weeklyschedule/CMakeLists.txt +++ b/test/bacnet/weeklyschedule/CMakeLists.txt @@ -57,6 +57,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/timestamp.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/wp/CMakeLists.txt b/test/bacnet/wp/CMakeLists.txt index a19910f4..6aaa2f88 100644 --- a/test/bacnet/wp/CMakeLists.txt +++ b/test/bacnet/wp/CMakeLists.txt @@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/wpm/CMakeLists.txt b/test/bacnet/wpm/CMakeLists.txt index f8ef6a1f..c362c2d8 100644 --- a/test/bacnet/wpm/CMakeLists.txt +++ b/test/bacnet/wpm/CMakeLists.txt @@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME} ${SRC_DIR}/bacnet/dailyschedule.c ${SRC_DIR}/bacnet/calendar_entry.c ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c # Test and test library files ./src/main.c ${ZTST_DIR}/ztest_mock.c diff --git a/test/bacnet/write_group/CMakeLists.txt b/test/bacnet/write_group/CMakeLists.txt new file mode 100644 index 00000000..79c08c53 --- /dev/null +++ b/test/bacnet/write_group/CMakeLists.txt @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/write_group.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/access_rule.c + ${SRC_DIR}/bacnet/bacaction.c + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacerror.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + ${SRC_DIR}/bacnet/calendar_entry.c + ${SRC_DIR}/bacnet/special_event.c + ${SRC_DIR}/bacnet/channel_value.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/write_group/src/main.c b/test/bacnet/write_group/src/main.c new file mode 100644 index 00000000..fff7e34f --- /dev/null +++ b/test/bacnet/write_group/src/main.c @@ -0,0 +1,292 @@ +/** + * @file + * @brief Unit test for WriteGroup service + * @author Steve Karg + * @date October 2024 + * + * SPDX-License-Identifier: MIT + */ +#include +#include + +#define WRITE_GROUP_CHANNEL_LIST_MAX 8 + +/** + * @addtogroup bacnet_tests + * @{ + */ + +static void test_WriteGroup_Postive(BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + int apdu_len = 0; + bool status; + uint8_t apdu[480] = { 0 }; + BACNET_WRITE_GROUP_DATA test_data = { 0 }; + + /* positive test */ + len = + bacnet_write_group_service_request_encode(&apdu[0], sizeof(apdu), data); + zassert_true(len > 0, NULL); + apdu_len = len; + len = bacnet_write_group_service_request_decode( + &apdu[0], apdu_len, &test_data); + zassert_not_equal(len, BACNET_STATUS_ERROR, NULL); + zassert_equal(len, apdu_len, NULL); + status = bacnet_write_group_same(data, &test_data); + zassert_true(status, NULL); + /* negative decoding test */ + while (--apdu_len) { + len = bacnet_write_group_service_request_decode( + apdu, apdu_len, &test_data); + /* inhibit delay is an optional element at the end of the + construction and the APDU is valid without it */ + if (data->inhibit_delay == WRITE_GROUP_INHIBIT_DELAY_NONE) { + zassert_true(len <= 0, NULL); + } + } + /* negative encoding test */ + len = + bacnet_write_group_service_request_encode(&apdu[0], sizeof(apdu), data); + zassert_true(len > 0, NULL); + apdu_len = len; + while (--apdu_len) { + len = + bacnet_write_group_service_request_encode(&apdu[0], apdu_len, data); + zassert_equal(len, 0, NULL); + } +} + +/** + * @addtogroup bacnet_tests + * @{ + */ + +static void test_WriteGroup_Negative(BACNET_WRITE_GROUP_DATA *data) +{ + int len = 0; + int apdu_len = 0; + uint8_t apdu[480] = { 0 }; + BACNET_WRITE_GROUP_DATA test_data = { 0 }; + + len = + bacnet_write_group_service_request_encode(&apdu[0], sizeof(apdu), data); + zassert_true(len > 0, NULL); + apdu_len = len; + len = bacnet_write_group_service_request_decode( + &apdu[0], apdu_len, &test_data); + zassert_true(len < 0, NULL); +} + +/** + * @brief callback for WriteGroup-Request iterator + * @param data [in] The contents of the WriteGroup-Request message + * @param change_list_index [in] The index of the current value in the change + * list + * @param change_list [in] The current value in the change list + */ +static void test_WriteGroup_Iterate_Value( + BACNET_WRITE_GROUP_DATA *data, + uint32_t change_list_index, + BACNET_GROUP_CHANNEL_VALUE *change_list) +{ + BACNET_GROUP_CHANNEL_VALUE *value = NULL; + + value = bacnet_write_group_change_list_element(data, change_list_index); + zassert_not_null(value, NULL); + zassert_true(bacnet_group_channel_value_same(value, change_list), NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(write_group_tests, test_WriteGroup_Iterate) +#else +static void test_WriteGroup_Iterate(void) +#endif +{ + int len, test_len; + uint8_t apdu[480] = { 0 }; + BACNET_WRITE_GROUP_DATA data = { + /* initial test values for sunny day use-case */ + .group_number = 1, + .write_priority = BACNET_MIN_PRIORITY, + .change_list = { 0 }, + .inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_NONE, + .next = NULL + }; + BACNET_GROUP_CHANNEL_VALUE value[WRITE_GROUP_CHANNEL_LIST_MAX] = { 0 }; + BACNET_GROUP_CHANNEL_VALUE value_append = { 0 }; + BACNET_GROUP_CHANNEL_VALUE *value_element = NULL; + unsigned count = 0; + unsigned index = 0; + bool status; + + /* link the array of change-list to our data */ + status = bacnet_write_group_change_list_array_link( + &data, value, ARRAY_SIZE(value)); + zassert_true(status, NULL); + /* note: array + head */ + for (index = 0; index < ARRAY_SIZE(value) + 1; index++) { + value_element = bacnet_write_group_change_list_element(&data, index); + zassert_not_null(value_element, NULL); + /* set some predictable values to be tested */ + value_element->channel = index; + value_element->overriding_priority = index; + value_element->value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value_element->value.type.Unsigned_Int = index; + } + len = bacnet_write_group_service_request_encode( + &apdu[0], sizeof(apdu), &data); + zassert_true(len > 0, NULL); + test_len = bacnet_write_group_service_request_decode_iterate( + &apdu[0], len, &data, test_WriteGroup_Iterate_Value); + count = bacnet_write_group_change_list_count(&data); + zassert_equal(count, WRITE_GROUP_CHANNEL_LIST_MAX + 1, NULL); + /* validate append API */ + value_append.channel = count; + value_append.overriding_priority = count; + value_append.value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; + value_append.value.type.Unsigned_Int = count; + status = bacnet_write_group_change_list_append(&data, &value_append); + zassert_true(status, NULL); + count = bacnet_write_group_change_list_count(&data); + zassert_equal(count, WRITE_GROUP_CHANNEL_LIST_MAX + 2, NULL); + len = bacnet_write_group_service_request_encode( + &apdu[0], sizeof(apdu), &data); + zassert_true(len > 0, NULL); + test_len = bacnet_write_group_service_request_decode_iterate( + &apdu[0], len, &data, test_WriteGroup_Iterate_Value); + /* some negative testing for coverage */ + count = bacnet_write_group_change_list_count(NULL); + zassert_equal(count, 0, NULL); + value_element = bacnet_write_group_change_list_element(NULL, 0); + zassert_true(value_element == NULL, NULL); + status = bacnet_write_group_change_list_array_link(NULL, NULL, 0); + zassert_false(status, NULL); + status = bacnet_write_group_change_list_append(NULL, NULL); + zassert_false(status, NULL); + status = bacnet_write_group_change_list_append(&data, NULL); + zassert_false(status, NULL); + status = bacnet_write_group_change_list_append(NULL, &value_append); + zassert_false(status, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(write_group_tests, test_WriteGroup) +#else +static void test_WriteGroup(void) +#endif +{ + int len = 0; + uint8_t apdu[480] = { 0 }; + BACNET_WRITE_GROUP_DATA data = { + /* initial test values for sunny day use-case */ + .group_number = 1, + .write_priority = BACNET_MIN_PRIORITY, + .change_list = { 0 }, + .inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_NONE, + .next = NULL + }; + + /* negative test */ + len = + bacnet_write_group_service_request_encode(&apdu[0], sizeof(apdu), NULL); + zassert_false(len > 0, NULL); + /* positive tests */ + data.inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_TRUE; + test_WriteGroup_Postive(&data); + data.inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_FALSE; + test_WriteGroup_Postive(&data); + data.inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_NONE; + test_WriteGroup_Postive(&data); + data.write_priority = 0; + test_WriteGroup_Negative(&data); + data.write_priority = BACNET_MAX_PRIORITY + 1; + test_WriteGroup_Negative(&data); + data.write_priority = BACNET_MAX_PRIORITY; + test_WriteGroup_Postive(&data); + data.group_number = 0; + test_WriteGroup_Negative(&data); + data.group_number = 1; +} +/** + * @} + */ + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(write_group_tests, test_WriteGroup_Same) +#else +static void test_WriteGroup_Same(void) +#endif +{ + bool status; + BACNET_WRITE_GROUP_DATA data = { + /* initial test values for sunny day use-case */ + .group_number = 1, + .write_priority = BACNET_MIN_PRIORITY, + .change_list = { 0 }, + .inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_NONE, + .next = NULL + }; + BACNET_WRITE_GROUP_DATA test_data = { 0 }; + + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + status = bacnet_write_group_copy(NULL, &data); + zassert_false(status, NULL); + status = bacnet_write_group_copy(&test_data, NULL); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + zassert_true(status, NULL); + status = bacnet_write_group_same(&data, &test_data); + zassert_true(status, NULL); + status = bacnet_write_group_same(NULL, &test_data); + zassert_false(status, NULL); + status = bacnet_write_group_same(&data, NULL); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.group_number = 0; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.write_priority = BACNET_MAX_PRIORITY; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.inhibit_delay = WRITE_GROUP_INHIBIT_DELAY_TRUE; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.change_list.channel = 1; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.change_list.overriding_priority = 1; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); + + status = bacnet_write_group_copy(&test_data, &data); + test_data.change_list.value.tag = BACNET_APPLICATION_TAG_BOOLEAN; + test_data.change_list.value.type.Boolean = true; + status = bacnet_write_group_same(&data, &test_data); + zassert_false(status, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(write_group_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite( + write_group_tests, ztest_unit_test(test_WriteGroup), + ztest_unit_test(test_WriteGroup_Same), + ztest_unit_test(test_WriteGroup_Iterate)); + + ztest_run_test_suite(write_group_tests); +} +#endif