Added WriteGroup service and Channel object interfaces (#829)

This commit is contained in:
Steve Karg
2024-10-25 10:43:29 -05:00
committed by GitHub
parent 9397cfaafb
commit 3329dff337
89 changed files with 4334 additions and 1022 deletions
+11
View File
@@ -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
$<$<BOOL:${UCI}>:src/bacnet/basic/ucix/ucix.c>
$<$<BOOL:${UCI}>: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})
+4
View File
@@ -204,6 +204,10 @@ whois:
writepropm:
$(MAKE) -s -C apps $@
.PHONY: writegroup
writegroup:
$(MAKE) -s -C apps $@
.PHONY: router
router:
$(MAKE) -s -C apps $@
+5 -1
View File
@@ -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 $@
+7 -15
View File
@@ -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);
+39
View File
@@ -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
+207
View File
@@ -0,0 +1,207 @@
/**
* @file
* @brief command line tool that sends a BACnet WriteGroup-Request message
* to the network
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2024
* @copyright SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h> /* for time */
#include <errno.h>
/* 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 <inhibit|delay>\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;
}
+417 -11
View File
@@ -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;
+8
View File
@@ -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,
+3 -1
View File
@@ -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, */
File diff suppressed because it is too large Load Diff
+12 -98
View File
@@ -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);
+111
View File
@@ -0,0 +1,111 @@
/**
* @file
* @brief The WriteGroup-Request service handler
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2024
* @copyright SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
/* 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");
}
}
+36
View File
@@ -0,0 +1,36 @@
/**
* @file
* @brief The WriteGroup-Request service handler
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2024
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef HANDLER_WRITE_GROUP_H
#define HANDLER_WRITE_GROUP_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h>
/* 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
+62
View File
@@ -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 <stddef.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
/* 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;
}
+30
View File
@@ -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 <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdint.h>
/* 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
+2
View File
@@ -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
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
/**
* @file
* @brief BACnet single precision REAL encode and decode functions
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2012
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_CHANNEL_VALUE_H
#define BACNET_CHANNEL_VALUE_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/* 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
+4 -1
View File
@@ -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 */
+700
View File
@@ -0,0 +1,700 @@
/**
* @file
* @brief WriteGroup service encode and decode
* @author Steve Karg <skarg@users.sourceforge.net>
* @date August 2023
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <stdint.h>
#include <stdbool.h>
/* 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;
}
+154
View File
@@ -0,0 +1,154 @@
/**
* @file
* @brief API for BACnet WriteGroup service encoder and decoder
* @author Steve Karg <skarg@users.sourceforge.net>
* @date 2024
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_WRITE_GROUP_H
#define BACNET_WRITE_GROUP_H
#include <stdint.h>
#include <stdbool.h>
/* 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
+3 -1
View File
@@ -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/*
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
@@ -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
+1
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+196 -103
View File
@@ -10,6 +10,7 @@
#include <zephyr/ztest.h>
#include <bacnet/basic/object/channel.h>
#include <bacnet/bactext.h>
#include <property_test.h>
/**
* @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);
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+50
View File
@@ -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
)
+147
View File
@@ -0,0 +1,147 @@
/**
* @file
* @brief Unit test for BACnetChannelValue.
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2024
* @copyright SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <zephyr/ztest.h>
#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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+1 -1
View File
@@ -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
+1
View File
@@ -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
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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
+66
View File
@@ -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
)
+292
View File
@@ -0,0 +1,292 @@
/**
* @file
* @brief Unit test for WriteGroup service
* @author Steve Karg <skarg@users.sourceforge.net>
* @date October 2024
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/ztest.h>
#include <bacnet/write_group.h>
#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