From 04b15064a237f76e7e2df98d06eda779d4afa028 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Mon, 17 Nov 2025 14:12:34 -0600 Subject: [PATCH 01/13] Added Loop object and MAX_APDU to BACnet Kconfig options and CMakeLists.txt defines. --- zephyr/CMakeLists.txt | 3 +++ zephyr/Kconfig | 12 ++++++++++++ zephyr/samples/profiles/b-asc/prj.conf | 1 + 3 files changed, 16 insertions(+) diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 57928da..0f6d181 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -28,6 +28,7 @@ message(STATUS "BACNETSTACK: BACNET_PROTOCOL_REVISION \"${CONFIG_BACNET_PROTOCOL message(STATUS "BACNETSTACK: BACAPP_MINIMAL \"${CONFIG_BACAPP_MINIMAL}\"") message(STATUS "BACNETSTACK: BACAPP_TYPES_EXTRA \"${CONFIG_BACAPP_TYPES_EXTRA}\"") message(STATUS "BACNETSTACK: BACNET_ADDRESS_CACHE_FILE \"${CONFIG_BACNET_ADDRESS_CACHE_FILE}\"") +message(STATUS "BACNETSTACK: MAX_APDU \"${CONFIG_CONFIG_BACNET_MAX_APDU}\"") message(STATUS "BACNETSTACK: MAX_TSM_TRANSACTIONS \"${CONFIG_BACNET_MAX_TSM_TRANSACTIONS}\"") message(STATUS "BACNETSTACK: MAX_ADDRESS_CACHE \"${CONFIG_BACNET_MAX_ADDRESS_CACHE}\"") message(STATUS "BACNETSTACK: MAX_CHARACTER_STRING_BYTES \"${CONFIG_BACNET_MAX_CHARACTER_STRING_BYTES}\"") @@ -476,6 +477,7 @@ set(BACNETSTACK_BASIC_SRCS $<$:${BACNETSTACK_SRC}/bacnet/basic/object/gateway/gw_device.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/iv.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/lc.c> + $<$:${BACNETSTACK_SRC}/bacnet/basic/object/loop.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/lo.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/blo.c> $<$:${BACNETSTACK_SRC}/bacnet/basic/object/lsp.c> @@ -595,6 +597,7 @@ zephyr_compile_definitions( $<$:BACAPP_PRINT_ENABLED=1> $<$:BACAPP_SNPRINTF_ENABLED=1> $<$:BACNET_ADDRESS_CACHE_FILE=1> + MAX_APDU=${CONFIG_BACNET_MAX_APDU} MAX_TSM_TRANSACTIONS=${CONFIG_BACNET_MAX_TSM_TRANSACTIONS} MAX_ADDRESS_CACHE=${CONFIG_BACNET_MAX_ADDRESS_CACHE} MAX_CHARACTER_STRING_BYTES=${CONFIG_BACNET_MAX_CHARACTER_STRING_BYTES} diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 36a8d6a..7c83f33 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -647,6 +647,12 @@ config BACDL_BIP6_PORT help UDP port to listen on (default=47808) +config BACNET_MAX_APDU + int "Maximum size of the APDU" + default 1476 + help + Maximum size of the APDU (default=1476) + config BACNET_MAX_TSM_TRANSACTIONS int "Number of initiated confirmed-message transactions" default 1 @@ -814,6 +820,12 @@ config BACNET_BASIC_OBJECT_LOAD_CONTROL help Use the BACnet basic load control object +config BACNET_BASIC_OBJECT_LOOP + bool "Use the BACnet basic loop object" + default false + help + Use the BACnet basic loop object + config BACNET_BASIC_OBJECT_LIGHTING_OUTPUT bool "Use the BACnet basic lighting output object" default false diff --git a/zephyr/samples/profiles/b-asc/prj.conf b/zephyr/samples/profiles/b-asc/prj.conf index 582a880..c9ac1bb 100644 --- a/zephyr/samples/profiles/b-asc/prj.conf +++ b/zephyr/samples/profiles/b-asc/prj.conf @@ -53,6 +53,7 @@ CONFIG_BACNET_BASIC_OBJECT_COLOR_TEMPERATURE=y CONFIG_BACNET_BASIC_OBJECT_CHARACTERSTRING_VALUE=y CONFIG_BACNET_BASIC_OBJECT_INTEGER_VALUE=y CONFIG_BACNET_BASIC_OBJECT_LOAD_CONTROL=y +CONFIG_BACNET_BASIC_OBJECT_LOOP=y CONFIG_BACNET_BASIC_OBJECT_LIGHTING_OUTPUT=y CONFIG_BACNET_BASIC_OBJECT_BINARY_LIGHTING_OUTPUT=y CONFIG_BACNET_BASIC_OBJECT_LIFE_SAFETY_POINT=y From 84968c2dc471b887a1a52d4b50828d2cf046f614 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 20 Nov 2025 17:24:23 -0600 Subject: [PATCH 02/13] [WIP] Added property shell to test objects by read and write property values. --- zephyr/CMakeLists.txt | 6 +- zephyr/Kconfig | 8 +- zephyr/samples/profiles/b-asc/prj.conf | 4 +- zephyr/subsys/bacnet_shell/CMakeLists.txt | 1 + .../bacnet_shell/bacnet_shell_objects.c | 24 +- .../bacnet_shell/bacnet_shell_property.c | 613 ++++++++++++++++++ 6 files changed, 639 insertions(+), 17 deletions(-) create mode 100644 zephyr/subsys/bacnet_shell/bacnet_shell_property.c diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 0f6d181..7b9b465 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -28,11 +28,12 @@ message(STATUS "BACNETSTACK: BACNET_PROTOCOL_REVISION \"${CONFIG_BACNET_PROTOCOL message(STATUS "BACNETSTACK: BACAPP_MINIMAL \"${CONFIG_BACAPP_MINIMAL}\"") message(STATUS "BACNETSTACK: BACAPP_TYPES_EXTRA \"${CONFIG_BACAPP_TYPES_EXTRA}\"") message(STATUS "BACNETSTACK: BACNET_ADDRESS_CACHE_FILE \"${CONFIG_BACNET_ADDRESS_CACHE_FILE}\"") -message(STATUS "BACNETSTACK: MAX_APDU \"${CONFIG_CONFIG_BACNET_MAX_APDU}\"") +message(STATUS "BACNETSTACK: MAX_APDU \"${CONFIG_BACNET_MAX_APDU_SIZE}\"") message(STATUS "BACNETSTACK: MAX_TSM_TRANSACTIONS \"${CONFIG_BACNET_MAX_TSM_TRANSACTIONS}\"") message(STATUS "BACNETSTACK: MAX_ADDRESS_CACHE \"${CONFIG_BACNET_MAX_ADDRESS_CACHE}\"") message(STATUS "BACNETSTACK: MAX_CHARACTER_STRING_BYTES \"${CONFIG_BACNET_MAX_CHARACTER_STRING_BYTES}\"") message(STATUS "BACNETSTACK: MAX_OCTET_STRING_BYTES \"${CONFIG_BACNET_MAX_OCTET_STRING_BYTES}\"") +message(STATUS "BACNETSTACK: BACNET_DAILY_SCHEDULE_TIME_VALUES_SIZE \"${CONFIG_BACNET_DAILY_SCHEDULE_TIME_VALUES_SIZE}\"") message(STATUS "BACNETSTACK: CONFIG_BACNETSTACK_LOG_LEVEL \"${CONFIG_BACNETSTACK_LOG_LEVEL}\"") if(NOT (CONFIG_BACDL_ETHERNET OR @@ -597,11 +598,12 @@ zephyr_compile_definitions( $<$:BACAPP_PRINT_ENABLED=1> $<$:BACAPP_SNPRINTF_ENABLED=1> $<$:BACNET_ADDRESS_CACHE_FILE=1> - MAX_APDU=${CONFIG_BACNET_MAX_APDU} + MAX_APDU=${CONFIG_BACNET_MAX_APDU_SIZE} MAX_TSM_TRANSACTIONS=${CONFIG_BACNET_MAX_TSM_TRANSACTIONS} MAX_ADDRESS_CACHE=${CONFIG_BACNET_MAX_ADDRESS_CACHE} MAX_CHARACTER_STRING_BYTES=${CONFIG_BACNET_MAX_CHARACTER_STRING_BYTES} MAX_OCTET_STRING_BYTES=${CONFIG_BACNET_MAX_OCTET_STRING_BYTES} + BACNET_DAILY_SCHEDULE_TIME_VALUES_SIZE=${CONFIG_BACNET_DAILY_SCHEDULE_TIME_VALUES_SIZE} $<$:MAX_COV_SUBCRIPTIONS=${CONFIG_BACNET_BASIC_COV_SUBSCRIPTIONS_SIZE}> $<$:MAX_COV_ADDRESSES=${CONFIG_BACNET_BASIC_COV_ADDRESSES_SIZE}> # BACnet data types supported for WriteProperty: all = minimal + extra diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 7c83f33..b2b1a76 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -254,6 +254,12 @@ config BACAPP_WEEKLY_SCHEDULE help BACnet data types supported for WriteProperty: WEEKLY_SCHEDULE +config BACNET_DAILY_SCHEDULE_TIME_VALUES_SIZE + int "Number of daily schedules for each of the 7 weekly schedules." + default 8 + help + Number of daily schedules for each of the 7 weekly schedules. (default=8) + config BACAPP_CALENDAR_ENTRY bool "BACnet data types supported for WriteProperty: CALENDAR_ENTRY" default false @@ -647,7 +653,7 @@ config BACDL_BIP6_PORT help UDP port to listen on (default=47808) -config BACNET_MAX_APDU +config BACNET_MAX_APDU_SIZE int "Maximum size of the APDU" default 1476 help diff --git a/zephyr/samples/profiles/b-asc/prj.conf b/zephyr/samples/profiles/b-asc/prj.conf index c9ac1bb..158dc20 100644 --- a/zephyr/samples/profiles/b-asc/prj.conf +++ b/zephyr/samples/profiles/b-asc/prj.conf @@ -15,11 +15,12 @@ CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_MAIN_THREAD_PRIORITY=7 #CONFIG_PICOLIBC=y CONFIG_ISR_STACK_SIZE=8192 -CONFIG_MAIN_STACK_SIZE=16384 CONFIG_IDLE_STACK_SIZE=2048 CONFIG_INIT_STACKS=y CONFIG_NET_TX_STACK_SIZE=8192 CONFIG_NET_RX_STACK_SIZE=8192 +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_SHELL_STACK_SIZE=32768 CONFIG_BACNET_BASIC_SERVER_KSLEEP=100 # BACnet Library @@ -27,6 +28,7 @@ CONFIG_BACNETSTACK=y # BACnet Library - shell CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y +CONFIG_BACAPP_PRINT_ENABLED=y # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y diff --git a/zephyr/subsys/bacnet_shell/CMakeLists.txt b/zephyr/subsys/bacnet_shell/CMakeLists.txt index ba41007..9b52da2 100644 --- a/zephyr/subsys/bacnet_shell/CMakeLists.txt +++ b/zephyr/subsys/bacnet_shell/CMakeLists.txt @@ -16,5 +16,6 @@ zephyr_library_sources_ifdef(CONFIG_BACNETSTACK_BACNET_BASIC_SHELL bacnet_shell_lighting_output.c bacnet_shell_objects.c bacnet_shell_packets.c + bacnet_shell_property.c bacnet_shell_uptime.c ) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_objects.c b/zephyr/subsys/bacnet_shell/bacnet_shell_objects.c index 583a60f..0706e38 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_objects.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_objects.c @@ -13,6 +13,7 @@ #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bactext.h" +#include "bacnet/bacstr.h" #include "bacnet/bacapp.h" /* BACnet objects API */ #include "bacnet/basic/object/device.h" @@ -33,39 +34,36 @@ static int bacnet_object_instance_parse( uint16_t *r_object_type, uint32_t *r_object_instance) { - uint16_t object_type = 0; + uint32_t object_type = 0; uint32_t object_instance = 0; - unsigned unsigned_value = 0; if (argc < 2) { shell_error(sh, "parse: %s ", argv[0]); return -EINVAL; } - if (!bactext_object_type_strtol(argv[1], &unsigned_value)) { + if (!bactext_object_type_strtol(argv[1], &object_type)) { shell_error(sh, "parse: Invalid object-type: %s.", argv[1]); return -EINVAL; } - if (unsigned_value > BACNET_MAX_OBJECT) { + if (object_type > BACNET_MAX_OBJECT) { shell_error( sh, "parse: Invalid object-type: %s. Must be 0-%u.", argv[1], BACNET_MAX_OBJECT); return -EINVAL; } - object_type = (uint16_t)unsigned_value; - if (!bactext_strtoul(argv[2], &unsigned_value)) { + if (r_object_type) { + *r_object_type = (uint16_t)object_type; + } + if (!bacnet_string_to_uint32(argv[2], &object_instance)) { shell_error(sh, "parse: Invalid object-instance: %s.", argv[2]); return -EINVAL; } - if (unsigned_value > 4194303) { + if (object_instance > BACNET_MAX_INSTANCE) { shell_error( - sh, "parse: Invalid object-instance: %s. Must be 0-4194303.", - argv[2]); + sh, "parse: Invalid object-instance: %s. Must be 0-%d.", argv[2], + BACNET_MAX_INSTANCE); return -EINVAL; } - object_instance = (uint32_t)unsigned_value; - if (r_object_type) { - *r_object_type = object_type; - } if (r_object_instance) { *r_object_instance = object_instance; } diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c new file mode 100644 index 0000000..37032fe --- /dev/null +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -0,0 +1,613 @@ +/** + * @file + * @brief The BACnet shell commands for debugging and testing property values + * @author Steve Karg + * @date November 2025 + * @copyright SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Parse a BACnet object type and instance + * @param sh Shell context + * @param argc Number of arguments + * @param argv Argument list + * @param object_type Pointer to the object type + * @param object_instance Pointer to the object instance + * @return 0 on success, negative on failure + * @note used by the shell to parse arguments + */ +int bacnet_shell_object_type_instance_parse( + const struct shell *sh, + size_t argc, + char **argv, + uint16_t *object_type, + uint32_t *object_instance) +{ + uint32_t found_index = 0; + + if (argc < 1) { + shell_help(sh); + return -EINVAL; + } + if (!bactext_object_type_strtol(argv[1], &found_index)) { + shell_error(sh, "ERROR: invalid object-type"); + return -EINVAL; + } + if ((found_index < 0) || (found_index >= UINT16_MAX)) { + shell_error( + sh, + "ERROR: Invalid object-type: %s. " + "Must be 0-%u.", + argv[1], UINT16_MAX); + return -EINVAL; + } + if (object_type) { + *object_type = (uint16_t)found_index; + } + if (found_index == OBJECT_DEVICE) { + if (object_instance) { + *object_instance = Device_Object_Instance_Number(); + } + } else if (argc > 1) { + if (!bacnet_string_to_uint32(argv[2], &found_index)) { + shell_error( + sh, + "parse: Invalid object-instance: %s. " + "Must be 0-%u.", + argv[2], BACNET_MAX_INSTANCE); + return -EINVAL; + } + if (object_instance) { + *object_instance = found_index; + } + } else { + shell_help(sh); + return -EINVAL; + } + + return 0; +} + +/** + * @brief Parse a BACnet object type, instance, property, and array index + * @param sh Shell context + * @param argc Number of arguments + * @param argv Argument list + * @param object_type Pointer to the object type + * @param object_instance Pointer to the object instance + * @param property_id Pointer to the property id + * @param array_index Pointer to the array index + * @return 0 on success, negative on failure + * @note used by the shell to parse arguments + */ +int bacnet_shell_property_parse( + const struct shell *sh, + size_t argc, + char **argv, + uint16_t *object_type, + uint32_t *object_instance, + uint32_t *property_id, + uint32_t *array_index) +{ + unsigned long unsigned_value = 0; + unsigned array_value = 0; + uint32_t found_index = 0; + char name[80] = ""; + int scan_count = 0; + int err; + + if (array_index) { + *array_index = BACNET_ARRAY_ALL; + } + if (argc < 4) { + shell_error( + sh, "parse: %s [index] [value]", + argv[0]); + return -EINVAL; + } + err = bacnet_shell_object_type_instance_parse( + sh, argc, argv, object_type, object_instance); + /* property can have [] to denote array */ + if (isalpha(argv[3][0])) { + /* choose a property by name */ + scan_count = sscanf(argv[3], "%79s[%u]", name, &array_value); + if (scan_count < 1) { + shell_error(sh, "parse: missing property: %s.", argv[3]); + return -EINVAL; + } + if (!bactext_property_strtol(name, &found_index)) { + shell_error(sh, "parse: invalid property name: %s.", argv[3]); + return -EINVAL; + } + if (property_id) { + *property_id = found_index; + } + + } else { + /* choose a property by number */ + scan_count = sscanf(argv[3], "%lu[%u]", &unsigned_value, &array_value); + if (scan_count < 1) { + shell_error(sh, "parse: missing property: %s.", argv[3]); + return -EINVAL; + } + if (unsigned_value > UINT32_MAX) { + shell_error( + sh, "parse: Invalid property: %s. Must be 0-%lu.", argv[3], + UINT32_MAX); + return -EINVAL; + } + if (property_id) { + *property_id = (uint32_t)unsigned_value; + } + } + if (scan_count >= 2) { + if (array_value > UINT32_MAX) { + shell_error( + sh, "parse: Invalid array index: %s. Must be 0-%lu.", argv[3], + UINT32_MAX); + return -EINVAL; + } + if (array_index) { + *array_index = (uint32_t)array_value; + } + } + + return 0; +} + +/** + * @brief Initialize the WriteProperty parameter structure data from the + * ReadProperty parameter structure data and the length of the property value + * @param wpdata [in,out] The structure to hold the WriteProperty request + * @param rpdata [in] The structure to hold the ReadProperty request + * @param len [in] ReadProperty application data length + */ +static void bacnet_shell_write_property_parameter_init( + BACNET_WRITE_PROPERTY_DATA *wpdata, + BACNET_READ_PROPERTY_DATA *rpdata, + int len) +{ + if (wpdata && rpdata) { + /* WriteProperty parameters */ + wpdata->object_type = rpdata->object_type; + wpdata->object_instance = rpdata->object_instance; + wpdata->object_property = rpdata->object_property; + wpdata->array_index = rpdata->array_index; + if (len > 0) { + memcpy( + &wpdata->application_data, rpdata->application_data, + sizeof(wpdata->application_data)); + wpdata->application_data_len = len; + } else { + wpdata->application_data_len = 0; + } + wpdata->priority = BACNET_NO_PRIORITY; + wpdata->error_code = ERROR_CODE_SUCCESS; + } +} + +/** + * @brief Get or set a BACnet object property value + * @param sh Shell + * @return 0 on success, negative on failure + */ +static int cmd_value_print( + const struct shell *sh, + BACNET_READ_PROPERTY_DATA *rpdata, + BACNET_APPLICATION_DATA_VALUE *value, + int apdu_len, + bool skip_print, + bool last) +{ + BACNET_OBJECT_PROPERTY_VALUE object_value = { 0 }; + uint8_t *apdu; + int len, str_len; + bool first_value = true; + bool last_value = false; + bool list_value = false; + + apdu = rpdata->application_data; + for (;;) { + len = bacapp_decode_known_array_property( + apdu, apdu_len, value, rpdata->object_type, rpdata->object_property, + rpdata->array_index); + if (len < 0) { + break; + } + if (first_value && (len < apdu_len)) { + list_value = true; + } + if (len >= apdu_len) { + last_value = true; + } + /* configure object-value for printing */ + object_value.object_type = rpdata->object_type; + object_value.object_instance = rpdata->object_instance; + object_value.object_property = rpdata->object_property; + object_value.array_index = rpdata->array_index; + object_value.value = value; + if (!skip_print) { + str_len = bacapp_snprintf_value(NULL, 0, &object_value); + if (str_len > 0) { + char str[str_len + 1]; + bacapp_snprintf_value(str, str_len + 1, &object_value); + shell_print( + sh, "%s{\"%s\": \"%s\"}%s%s", + (first_value && list_value) ? "[" : "", + bactext_property_name(object_value.object_property), str, + (last_value && list_value) ? "]" : "", last ? "]" : ","); + } else { + shell_hexdump(sh, apdu, apdu_len); + } + } + first_value = false; + if (last_value) { + break; + } + apdu_len -= len; + if (apdu) { + apdu += len; + } + } + + return 0; +} + +/** + * @brief Get or set a BACnet object property value + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_print_value_all( + const struct shell *sh, + BACNET_READ_PROPERTY_DATA *rpdata, + BACNET_APPLICATION_DATA_VALUE *value) +{ + struct special_property_list_t pPropertyList = { 0 }; + unsigned count = 0, index = 0, counter = 0; + int apdu_len, err; + bool skip_print = false; + bool last; + + if (!rpdata) { + return -EINVAL; + } + Device_Objects_Property_List( + rpdata->object_type, rpdata->object_instance, &pPropertyList); + count = pPropertyList.Required.count + pPropertyList.Optional.count + + pPropertyList.Proprietary.count; + /* display the property-list as well formed JSON */ + shell_print( + sh, "{\"%s\":{\"%s\":%u},", + bactext_property_name(PROP_OBJECT_IDENTIFIER), + bactext_object_type_name(rpdata->object_type), rpdata->object_instance); + shell_print(sh, "\"%s\": [", bactext_property_name(PROP_PROPERTY_LIST)); + index = 0; + while (pPropertyList.Required.pList[index] != -1) { + counter++; + rpdata->object_property = pPropertyList.Required.pList[index]; + apdu_len = Device_Read_Property(rpdata); + if (apdu_len >= 0) { + if (counter == count) { + last = true; + } + err = + cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); + } else { + shell_print( + sh, "ReadProperty ERROR:%s:%s", + bactext_error_class_name(rpdata->error_class), + bactext_error_code_name(rpdata->error_code)); + } + index++; + } + index = 0; + while (pPropertyList.Optional.pList[index] != -1) { + counter++; + rpdata->object_property = pPropertyList.Required.pList[index]; + apdu_len = Device_Read_Property(rpdata); + if (apdu_len >= 0) { + if (counter == count) { + last = true; + } + err = + cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); + } else { + shell_print( + sh, "ReadProperty ERROR:%s:%s", + bactext_error_class_name(rpdata->error_class), + bactext_error_code_name(rpdata->error_code)); + } + index++; + } + index = 0; + while (pPropertyList.Proprietary.pList[index] != -1) { + counter++; + rpdata->object_property = pPropertyList.Required.pList[index]; + apdu_len = Device_Read_Property(rpdata); + if (apdu_len >= 0) { + if (counter == count) { + last = true; + } + err = + cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); + } else { + shell_print( + sh, "ReadProperty ERROR:%s:%s", + bactext_error_class_name(rpdata->error_class), + bactext_error_code_name(rpdata->error_code)); + } + index++; + } + shell_print(sh, "\"property-list-size\": %d}", count); + + return 0; +} + +/** + * @brief Get or set a BACnet object property value + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_value(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t apdu[CONFIG_BACNET_MAX_APDU_SIZE] = { 0 }; + int err = 0, apdu_len = 0; + bool status = false; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_WRITE_PROPERTY_DATA wpdata = { 0 }; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + uint16_t object_type = 0; + uint32_t object_instance = 0; + uint32_t object_property = MAX_BACNET_PROPERTY_ID; + uint32_t array_index = BACNET_ARRAY_ALL; + uint32_t found_index = 0; + long priority = BACNET_NO_PRIORITY; + char *value_string = NULL; + bool null_value = false; + bool skip_print = false; + bool print_all_properties = false; + + if ((argc == 2) || (argc == 3)) { + err = bacnet_shell_object_type_instance_parse( + sh, argc, argv, &object_type, &object_instance); + if (err == 0) { + print_all_properties = true; + } else { + return err; + } + } else if (argc > 3) { + err = bacnet_shell_property_parse( + sh, argc, argv, &object_type, &object_instance, &object_property, + &array_index); + if (err != 0) { + return err; + } + } else { + shell_help(sh); + return -EINVAL; + } + if (argc > 4) { + /* this is WriteProperty since there is a value string argument */ + value_string = argv[4]; + skip_print = true; + } + /* ReadProperty */ + rpdata.application_data = &apdu[0]; + rpdata.application_data_len = sizeof(apdu); + rpdata.object_type = object_type; + rpdata.object_instance = object_instance; + rpdata.object_property = object_property; + rpdata.array_index = array_index; + if (print_all_properties) { + cmd_print_value_all(sh, &rpdata, &value); + return 0; + } + apdu_len = Device_Read_Property(&rpdata); + if (apdu_len >= 0) { + err = cmd_value_print(sh, &rpdata, &value, apdu_len, skip_print, true); + } else { + shell_print( + sh, "ReadProperty ERROR:%s:%s", + bactext_error_class_name(rpdata.error_class), + bactext_error_code_name(rpdata.error_code)); + } + if (value_string) { + /* WriteProperty */ + /* allow for optional priority using @ symbol for commandables */ + if (property_list_commandable_member(object_type, object_property)) { + /* search the new_text for the @ symbol */ + char *at_ptr = strchr(value_string, '@'); + if (at_ptr) { + /* convert the priority value after the @ symbol + into an integer */ + priority = strtol(at_ptr + 1, NULL, 0); + if (priority < BACNET_MIN_PRIORITY) { + priority = BACNET_NO_PRIORITY; + } + if (priority > BACNET_MAX_PRIORITY) { + priority = BACNET_NO_PRIORITY; + } + /* null terminate the string at the @ symbol */ + *at_ptr = 0; + } + /* check for case insensitive NULL string */ + if (bacnet_strnicmp(value_string, "NULL", 4) == 0) { + null_value = true; + } + } + /* convert the string value into a tagged union value */ + if (null_value) { + value.tag = BACNET_APPLICATION_TAG_NULL; + status = true; + } else if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) { + status = bactext_object_property_strtoul( + (BACNET_OBJECT_TYPE)object_type, + (BACNET_PROPERTY_ID)object_property, value_string, + &found_index); + if (status) { + value.tag = BACNET_APPLICATION_TAG_ENUMERATED; + value.type.Enumerated = found_index; + } + } else { + status = + bacapp_parse_application_data(value.tag, value_string, &value); + } + shell_print( + sh, "Parsed %s-%u %s %s -> tag=%u %s", + bactext_object_type_name(object_type), object_instance, + bactext_property_name(object_property), value_string, value.tag, + status ? "successfully" : "unsuccessfully"); + if (status) { + apdu_len = bacapp_encode_data(apdu, &value); + if (apdu_len) { + bacnet_shell_write_property_parameter_init(&wpdata, &rpdata, 0); + wpdata.priority = priority; + wpdata.application_data_len = apdu_len; + memcpy(&wpdata.application_data, apdu, apdu_len); + status = Device_Write_Property(&wpdata); + if (status) { + shell_print(sh, "WriteProperty SUCCESS"); + } else { + shell_print( + sh, "WriteProperty ERROR:%s:%s", + bactext_error_class_name(wpdata.error_class), + bactext_error_code_name(wpdata.error_code)); + } + } else { + shell_print( + sh, "WriteProperty failed to encode application data"); + } + } + } + + return 0; +} + +/** + * @brief List all the BACnet property for a given object + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_list(const struct shell *sh, size_t argc, char **argv) +{ + uint16_t object_type = 0; + uint32_t object_instance = 0; + struct special_property_list_t pPropertyList = { 0 }; + unsigned count = 0, index = 0, counter = 0; + int err; + + if (argc > 2) { + err = bacnet_shell_object_type_instance_parse( + sh, argc, argv, &object_type, &object_instance); + if (err != 0) { + shell_help(sh); + return err; + } + } else { + shell_help(sh); + return -EINVAL; + } + Device_Objects_Property_List( + (BACNET_OBJECT_TYPE)object_type, object_instance, &pPropertyList); + count = pPropertyList.Required.count + pPropertyList.Optional.count + + pPropertyList.Proprietary.count; + /* display the property-list as well formed JSON */ + shell_print( + sh, "{\"%s\":{\"%s\":%u},", + bactext_property_name(PROP_OBJECT_IDENTIFIER), + bactext_object_type_name(object_type), object_instance); + shell_print(sh, "\"%s\": [", bactext_property_name(PROP_PROPERTY_LIST)); + index = 0; + while (pPropertyList.Required.pList[index] != -1) { + counter++; + shell_print( + sh, "\"%s\"%s", + bactext_property_name(pPropertyList.Required.pList[index]), + (counter == count) ? "]," : ","); + index++; + } + index = 0; + while (pPropertyList.Optional.pList[index] != -1) { + counter++; + shell_print( + sh, "\"%s\"%s", + bactext_property_name(pPropertyList.Required.pList[index]), + (counter == count) ? "]," : ","); + index++; + } + index = 0; + while (pPropertyList.Proprietary.pList[index] != -1) { + counter++; + shell_print( + sh, "%lu %s", (unsigned long)pPropertyList.Proprietary.pList[index], + (counter == count) ? "]," : ","); + index++; + } + shell_print(sh, "\"property-list-size\": %d}", count); + + return 0; +} + +/** + * @brief List all the BACnet property for a given object + * @param sh Shell + * @param argc Number of arguments + * @param argv Argument list + * @return 0 on success, negative on failure + */ +static int cmd_size(const struct shell *sh, size_t argc, char **argv) +{ + /* display the sizes as well formed JSON */ + shell_print( + sh, "{{\"read-property-data-size\": %lu},", + (unsigned long)sizeof(BACNET_READ_PROPERTY_DATA)); + shell_print( + sh, "{\"write-property-data-size\": %lu},", + (unsigned long)sizeof(BACNET_WRITE_PROPERTY_DATA)); + shell_print( + sh, "{\"application-data-value-size\": %lu},", + (unsigned long)sizeof(BACNET_APPLICATION_DATA_VALUE)); + shell_print( + sh, "{\"object-property-value-size\": %lu}}", + (unsigned long)sizeof(BACNET_OBJECT_PROPERTY_VALUE)); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE( + sub_bacnet_property_cmds, + SHELL_CMD(list, NULL, " ", cmd_list), + SHELL_CMD(size, NULL, "show the size of value data types", cmd_size), + SHELL_CMD( + value, + NULL, + " " + "[array index] [value]", + cmd_value), + SHELL_SUBCMD_SET_END); + +SHELL_SUBCMD_ADD( + (bacnet), + property, + &sub_bacnet_property_cmds, + "BACnet property commands", + NULL, + 1, + 0); From fcafdf793ce5ecb7b8a600aa36ea95d0d9c1301f Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 13:06:17 -0600 Subject: [PATCH 03/13] Reduce the STACK sizes for main and shell to 8K. --- zephyr/samples/profiles/b-asc/prj.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zephyr/samples/profiles/b-asc/prj.conf b/zephyr/samples/profiles/b-asc/prj.conf index 158dc20..e824668 100644 --- a/zephyr/samples/profiles/b-asc/prj.conf +++ b/zephyr/samples/profiles/b-asc/prj.conf @@ -11,7 +11,6 @@ # system CONFIG_KERNEL_MEM_POOL=y -CONFIG_HEAP_MEM_POOL_SIZE=8192 CONFIG_MAIN_THREAD_PRIORITY=7 #CONFIG_PICOLIBC=y CONFIG_ISR_STACK_SIZE=8192 @@ -19,8 +18,9 @@ CONFIG_IDLE_STACK_SIZE=2048 CONFIG_INIT_STACKS=y CONFIG_NET_TX_STACK_SIZE=8192 CONFIG_NET_RX_STACK_SIZE=8192 -CONFIG_MAIN_STACK_SIZE=16384 -CONFIG_SHELL_STACK_SIZE=32768 +CONFIG_MAIN_STACK_SIZE=8192 +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_SHELL_STACK_SIZE=8192 CONFIG_BACNET_BASIC_SERVER_KSLEEP=100 # BACnet Library From 8881f359b212f0425d6f794bbe34209281ca033f Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 13:10:03 -0600 Subject: [PATCH 04/13] Added "bacnet property" shell command with list, size, and value options. The value option can list all the property values of any internal object, and read or write any specific value properties that are writable. The output is in JSON format. --- .../bacnet_shell/bacnet_shell_property.c | 248 +++++++++++++----- 1 file changed, 187 insertions(+), 61 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index 37032fe..81a352b 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -196,6 +196,122 @@ static void bacnet_shell_write_property_parameter_init( } } +/** + * @brief determine if the JSON string value requires quoations or not + * @param value string value to analyze + * @return true if the string requires quotations + */ +static bool cmd_json_is_quoted(const char *value) +{ + bool quotes = false; + int digit = 0, len = 0, decimal_points = 0; + bool digits_only = false; + + /* In JSON, the treatment of values as quoted or numeric + depends on their data type: + String values: Must always be enclosed in double quotes. + Numeric values: Should not be enclosed in double quotes. + They are represented directly as numbers + (integers or floating-point numbers). + Boolean values: true and false are keywords and are not quoted. + Null value: null is a keyword and is not quoted. */ + if ((strcmp(value, "true") == 0) || (strcmp(value, "false") == 0) || + (strcmp(value, "null") == 0)) { + quotes = false; + } else { + len = strlen(value); + if (len && (value[0] == '"')) { + /* already quoted */ + quotes = false; + } else if (len) { + digits_only = true; + while (len) { + len--; + digit = value[len]; + if (!isdigit(digit)) { + if ((len == 0) && (value[len] == '-')) { + /* negative sign could be numeric */ + } else if (value[len] == '.') { + /* one decimal place could be numeric */ + decimal_points++; + if (decimal_points > 1) { + digits_only = false; + } + } else { + digits_only = false; + } + } + } + if (digits_only) { + quotes = false; + } else { + quotes = true; + } + } else { + /* empty value string */ + quotes = true; + } + } + + return quotes; +} + +static int cmd_json_print_key_value( + const struct shell *sh, + const char *prefix, + const char *key, + const char *value, + const char *suffix, + const char *append) +{ + const char *isquoted = ""; + + if (cmd_json_is_quoted(value)) { + isquoted = "\""; + } + shell_print( + sh, "%s{\"%s\": %s%s%s}%s%s", prefix, key, isquoted, value, isquoted, + suffix, append); + + return 0; +} + +static int cmd_json_print_hex_dump( + const struct shell *sh, + const char *prefix, + const char *key, + const char *buffer, + int buffer_length, + const char *suffix, + const char *append) +{ + char str[(buffer_length * 2) + 1]; + int i, s, slen; + + s = 0; + for (i = 0; i < buffer_length; i++) { + slen = sprintf(&str[s], "%02x", buffer[i]); + s += slen; + } + cmd_json_print_key_value(sh, prefix, key, str, suffix, append); + + return 0; +} + +static int cmd_json_print_key_error( + const struct shell *sh, + const char *key, + const char *error_class, + const char *error_code, + const char *append) +{ + shell_print( + sh, "{\"%s\": {\"error-class\": %s}, {\"error-code\": %s}}%s", key, + error_class, error_code, append); + + return 0; +} + /** * @brief Get or set a BACnet object property value * @param sh Shell @@ -207,7 +323,7 @@ static int cmd_value_print( BACNET_APPLICATION_DATA_VALUE *value, int apdu_len, bool skip_print, - bool last) + const char *append) { BACNET_OBJECT_PROPERTY_VALUE object_value = { 0 }; uint8_t *apdu; @@ -215,13 +331,33 @@ static int cmd_value_print( bool first_value = true; bool last_value = false; bool list_value = false; + const char *prefix = ""; + const char *suffix = ""; + char name[80] = ""; + if (apdu_len < 0) { + if (bactext_property_name_proprietary(rpdata->object_property)) { + cmd_json_print_key_error( + sh, bacnet_ultoa(rpdata->object_property, name, sizeof(name)), + bactext_error_class_name(rpdata->error_class), + bactext_error_code_name(rpdata->error_code), append); + } else { + cmd_json_print_key_error( + sh, bactext_property_name(rpdata->object_property), + bactext_error_class_name(rpdata->error_class), + bactext_error_code_name(rpdata->error_code), append); + } + return 0; + } apdu = rpdata->application_data; for (;;) { len = bacapp_decode_known_array_property( apdu, apdu_len, value, rpdata->object_type, rpdata->object_property, rpdata->array_index); if (len < 0) { + cmd_json_print_hex_dump( + sh, prefix, bactext_property_name(object_value.object_property), + apdu, apdu_len, suffix, append); break; } if (first_value && (len < apdu_len)) { @@ -237,17 +373,29 @@ static int cmd_value_print( object_value.array_index = rpdata->array_index; object_value.value = value; if (!skip_print) { + if (first_value && list_value) { + prefix = "["; + } else { + prefix = ""; + } + if (last_value && list_value) { + suffix = "]"; + } else { + suffix = ""; + } str_len = bacapp_snprintf_value(NULL, 0, &object_value); if (str_len > 0) { char str[str_len + 1]; bacapp_snprintf_value(str, str_len + 1, &object_value); - shell_print( - sh, "%s{\"%s\": \"%s\"}%s%s", - (first_value && list_value) ? "[" : "", + cmd_json_print_key_value( + sh, prefix, bactext_property_name(object_value.object_property), str, - (last_value && list_value) ? "]" : "", last ? "]" : ","); + suffix, append); } else { - shell_hexdump(sh, apdu, apdu_len); + cmd_json_print_hex_dump( + sh, prefix, + bactext_property_name(object_value.object_property), apdu, + len, suffix, append); } } first_value = false; @@ -279,7 +427,7 @@ static int cmd_print_value_all( unsigned count = 0, index = 0, counter = 0; int apdu_len, err; bool skip_print = false; - bool last; + char *append = ","; if (!rpdata) { return -EINVAL; @@ -290,7 +438,7 @@ static int cmd_print_value_all( pPropertyList.Proprietary.count; /* display the property-list as well formed JSON */ shell_print( - sh, "{\"%s\":{\"%s\":%u},", + sh, "{\"%s\":\"(%s:%u)\",", bactext_property_name(PROP_OBJECT_IDENTIFIER), bactext_object_type_name(rpdata->object_type), rpdata->object_instance); shell_print(sh, "\"%s\": [", bactext_property_name(PROP_PROPERTY_LIST)); @@ -299,18 +447,10 @@ static int cmd_print_value_all( counter++; rpdata->object_property = pPropertyList.Required.pList[index]; apdu_len = Device_Read_Property(rpdata); - if (apdu_len >= 0) { - if (counter == count) { - last = true; - } - err = - cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); - } else { - shell_print( - sh, "ReadProperty ERROR:%s:%s", - bactext_error_class_name(rpdata->error_class), - bactext_error_code_name(rpdata->error_code)); + if (counter == count) { + append = "],"; } + err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); index++; } index = 0; @@ -318,18 +458,10 @@ static int cmd_print_value_all( counter++; rpdata->object_property = pPropertyList.Required.pList[index]; apdu_len = Device_Read_Property(rpdata); - if (apdu_len >= 0) { - if (counter == count) { - last = true; - } - err = - cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); - } else { - shell_print( - sh, "ReadProperty ERROR:%s:%s", - bactext_error_class_name(rpdata->error_class), - bactext_error_code_name(rpdata->error_code)); + if (counter == count) { + append = "],"; } + err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); index++; } index = 0; @@ -337,18 +469,10 @@ static int cmd_print_value_all( counter++; rpdata->object_property = pPropertyList.Required.pList[index]; apdu_len = Device_Read_Property(rpdata); - if (apdu_len >= 0) { - if (counter == count) { - last = true; - } - err = - cmd_value_print(sh, rpdata, value, apdu_len, skip_print, last); - } else { - shell_print( - sh, "ReadProperty ERROR:%s:%s", - bactext_error_class_name(rpdata->error_class), - bactext_error_code_name(rpdata->error_code)); + if (counter == count) { + append = "],"; } + err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); index++; } shell_print(sh, "\"property-list-size\": %d}", count); @@ -418,14 +542,7 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) return 0; } apdu_len = Device_Read_Property(&rpdata); - if (apdu_len >= 0) { - err = cmd_value_print(sh, &rpdata, &value, apdu_len, skip_print, true); - } else { - shell_print( - sh, "ReadProperty ERROR:%s:%s", - bactext_error_class_name(rpdata.error_class), - bactext_error_code_name(rpdata.error_code)); - } + err = cmd_value_print(sh, &rpdata, &value, apdu_len, skip_print, ""); if (value_string) { /* WriteProperty */ /* allow for optional priority using @ symbol for commandables */ @@ -467,11 +584,13 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) status = bacapp_parse_application_data(value.tag, value_string, &value); } +#ifdef BACNET_SHELL_PROPERTY_DEBUG shell_print( sh, "Parsed %s-%u %s %s -> tag=%u %s", bactext_object_type_name(object_type), object_instance, bactext_property_name(object_property), value_string, value.tag, status ? "successfully" : "unsuccessfully"); +#endif if (status) { apdu_len = bacapp_encode_data(apdu, &value); if (apdu_len) { @@ -481,16 +600,21 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) memcpy(&wpdata.application_data, apdu, apdu_len); status = Device_Write_Property(&wpdata); if (status) { - shell_print(sh, "WriteProperty SUCCESS"); + cmd_json_print_key_error( + sh, bactext_property_name(wpdata.object_property), + bactext_error_class_name(ERROR_CLASS_PROPERTY), + bactext_error_code_name(ERROR_CODE_SUCCESS), ""); } else { - shell_print( - sh, "WriteProperty ERROR:%s:%s", + cmd_json_print_key_error( + sh, bactext_property_name(wpdata.object_property), bactext_error_class_name(wpdata.error_class), - bactext_error_code_name(wpdata.error_code)); + bactext_error_code_name(wpdata.error_code), ""); } } else { - shell_print( - sh, "WriteProperty failed to encode application data"); + cmd_json_print_key_error( + sh, bactext_property_name(object_property), + bactext_error_class_name(ERROR_CLASS_PROPERTY), + bactext_error_code_name(ERROR_CODE_UNEXPECTED_DATA), ""); } } } @@ -513,7 +637,7 @@ static int cmd_list(const struct shell *sh, size_t argc, char **argv) unsigned count = 0, index = 0, counter = 0; int err; - if (argc > 2) { + if ((argc == 2) || (argc == 3)) { err = bacnet_shell_object_type_instance_parse( sh, argc, argv, &object_type, &object_instance); if (err != 0) { @@ -530,7 +654,7 @@ static int cmd_list(const struct shell *sh, size_t argc, char **argv) pPropertyList.Proprietary.count; /* display the property-list as well formed JSON */ shell_print( - sh, "{\"%s\":{\"%s\":%u},", + sh, "{\"%s\":\"(%s:%u)\",", bactext_property_name(PROP_OBJECT_IDENTIFIER), bactext_object_type_name(object_type), object_instance); shell_print(sh, "\"%s\": [", bactext_property_name(PROP_PROPERTY_LIST)); @@ -576,16 +700,18 @@ static int cmd_size(const struct shell *sh, size_t argc, char **argv) { /* display the sizes as well formed JSON */ shell_print( - sh, "{{\"read-property-data-size\": %lu},", + sh, "{\"read-property-data-size\": %lu,", (unsigned long)sizeof(BACNET_READ_PROPERTY_DATA)); shell_print( - sh, "{\"write-property-data-size\": %lu},", + sh, "\"application-data-unit-size\": %lu,", (unsigned long)MAX_APDU); + shell_print( + sh, "\"write-property-data-size\": %lu,", (unsigned long)sizeof(BACNET_WRITE_PROPERTY_DATA)); shell_print( - sh, "{\"application-data-value-size\": %lu},", + sh, "\"application-data-value-size\": %lu,", (unsigned long)sizeof(BACNET_APPLICATION_DATA_VALUE)); shell_print( - sh, "{\"object-property-value-size\": %lu}}", + sh, "\"object-property-value-size\": %lu}", (unsigned long)sizeof(BACNET_OBJECT_PROPERTY_VALUE)); return 0; From 176fa97a9a6dd192f424c426b6a104d32476ea32 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 13:16:00 -0600 Subject: [PATCH 05/13] Set stack for main, heap, and shell at 16K for B-ASC sample. --- zephyr/samples/profiles/b-asc/prj.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zephyr/samples/profiles/b-asc/prj.conf b/zephyr/samples/profiles/b-asc/prj.conf index e824668..be4c071 100644 --- a/zephyr/samples/profiles/b-asc/prj.conf +++ b/zephyr/samples/profiles/b-asc/prj.conf @@ -18,9 +18,9 @@ CONFIG_IDLE_STACK_SIZE=2048 CONFIG_INIT_STACKS=y CONFIG_NET_TX_STACK_SIZE=8192 CONFIG_NET_RX_STACK_SIZE=8192 -CONFIG_MAIN_STACK_SIZE=8192 -CONFIG_HEAP_MEM_POOL_SIZE=8192 -CONFIG_SHELL_STACK_SIZE=8192 +CONFIG_HEAP_MEM_POOL_SIZE=16384 +CONFIG_MAIN_STACK_SIZE=16384 +CONFIG_SHELL_STACK_SIZE=16384 CONFIG_BACNET_BASIC_SERVER_KSLEEP=100 # BACnet Library From ca2004d678ae94c659e81620ecbd882f648d2979 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 16:28:28 -0600 Subject: [PATCH 06/13] Fixed application data value initialization between uses. --- .../bacnet_shell/bacnet_shell_property.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index 81a352b..411ad4e 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -418,11 +418,10 @@ static int cmd_value_print( * @param argv Argument list * @return 0 on success, negative on failure */ -static int cmd_print_value_all( - const struct shell *sh, - BACNET_READ_PROPERTY_DATA *rpdata, - BACNET_APPLICATION_DATA_VALUE *value) +static int +cmd_print_value_all(const struct shell *sh, BACNET_READ_PROPERTY_DATA *rpdata) { + BACNET_APPLICATION_DATA_VALUE value = { 0 }; struct special_property_list_t pPropertyList = { 0 }; unsigned count = 0, index = 0, counter = 0; int apdu_len, err; @@ -450,7 +449,8 @@ static int cmd_print_value_all( if (counter == count) { append = "],"; } - err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); + bacapp_value_list_init(&value, 1); + err = cmd_value_print(sh, rpdata, &value, apdu_len, skip_print, append); index++; } index = 0; @@ -461,7 +461,8 @@ static int cmd_print_value_all( if (counter == count) { append = "],"; } - err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); + bacapp_value_list_init(&value, 1); + err = cmd_value_print(sh, rpdata, &value, apdu_len, skip_print, append); index++; } index = 0; @@ -472,7 +473,8 @@ static int cmd_print_value_all( if (counter == count) { append = "],"; } - err = cmd_value_print(sh, rpdata, value, apdu_len, skip_print, append); + bacapp_value_list_init(&value, 1); + err = cmd_value_print(sh, rpdata, &value, apdu_len, skip_print, append); index++; } shell_print(sh, "\"property-list-size\": %d}", count); @@ -538,10 +540,11 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) rpdata.object_property = object_property; rpdata.array_index = array_index; if (print_all_properties) { - cmd_print_value_all(sh, &rpdata, &value); + cmd_print_value_all(sh, &rpdata); return 0; } apdu_len = Device_Read_Property(&rpdata); + bacapp_value_list_init(&value, 1); err = cmd_value_print(sh, &rpdata, &value, apdu_len, skip_print, ""); if (value_string) { /* WriteProperty */ From 1d38b4533815327d4c3047f80bb3d54b110b235a Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 16:29:26 -0600 Subject: [PATCH 07/13] Updated SHELL stack for samples to 16K. --- zephyr/samples/profiles/b-asc/prj.conf | 2 +- zephyr/samples/profiles/b-ld/prj.conf | 2 ++ zephyr/samples/profiles/b-ls/prj.conf | 2 ++ zephyr/samples/profiles/b-sa/prj.conf | 2 ++ zephyr/samples/profiles/b-ss/prj.conf | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/zephyr/samples/profiles/b-asc/prj.conf b/zephyr/samples/profiles/b-asc/prj.conf index be4c071..aba39c1 100644 --- a/zephyr/samples/profiles/b-asc/prj.conf +++ b/zephyr/samples/profiles/b-asc/prj.conf @@ -20,7 +20,6 @@ CONFIG_NET_TX_STACK_SIZE=8192 CONFIG_NET_RX_STACK_SIZE=8192 CONFIG_HEAP_MEM_POOL_SIZE=16384 CONFIG_MAIN_STACK_SIZE=16384 -CONFIG_SHELL_STACK_SIZE=16384 CONFIG_BACNET_BASIC_SERVER_KSLEEP=100 # BACnet Library @@ -29,6 +28,7 @@ CONFIG_BACNETSTACK=y CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y CONFIG_BACAPP_PRINT_ENABLED=y +CONFIG_SHELL_STACK_SIZE=16384 # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y diff --git a/zephyr/samples/profiles/b-ld/prj.conf b/zephyr/samples/profiles/b-ld/prj.conf index faf8088..4131a6d 100644 --- a/zephyr/samples/profiles/b-ld/prj.conf +++ b/zephyr/samples/profiles/b-ld/prj.conf @@ -25,6 +25,8 @@ CONFIG_BACNETSTACK=y # BACnet Library - shell CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y +CONFIG_BACAPP_PRINT_ENABLED=y +CONFIG_SHELL_STACK_SIZE=16384 # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y diff --git a/zephyr/samples/profiles/b-ls/prj.conf b/zephyr/samples/profiles/b-ls/prj.conf index b76d148..3b88aa7 100644 --- a/zephyr/samples/profiles/b-ls/prj.conf +++ b/zephyr/samples/profiles/b-ls/prj.conf @@ -25,6 +25,8 @@ CONFIG_BACNETSTACK=y # BACnet Library - shell CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y +CONFIG_BACAPP_PRINT_ENABLED=y +CONFIG_SHELL_STACK_SIZE=16384 # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y diff --git a/zephyr/samples/profiles/b-sa/prj.conf b/zephyr/samples/profiles/b-sa/prj.conf index ae52635..75711f1 100644 --- a/zephyr/samples/profiles/b-sa/prj.conf +++ b/zephyr/samples/profiles/b-sa/prj.conf @@ -25,6 +25,8 @@ CONFIG_BACNETSTACK=y # BACnet Library - shell CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y +CONFIG_BACAPP_PRINT_ENABLED=y +CONFIG_SHELL_STACK_SIZE=16384 # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y diff --git a/zephyr/samples/profiles/b-ss/prj.conf b/zephyr/samples/profiles/b-ss/prj.conf index 7ec9ef3..1061ea2 100644 --- a/zephyr/samples/profiles/b-ss/prj.conf +++ b/zephyr/samples/profiles/b-ss/prj.conf @@ -25,6 +25,8 @@ CONFIG_BACNETSTACK=y # BACnet Library - shell CONFIG_BACNETSTACK_BACNET_SHELL=y CONFIG_BACNETSTACK_BACNET_BASIC_SHELL=y +CONFIG_BACAPP_PRINT_ENABLED=y +CONFIG_SHELL_STACK_SIZE=16384 # BACnet settings subsystem CONFIG_BACNETSTACK_BACNET_SETTINGS=y CONFIG_BACNET_SETTINGS_SHELL=y From 26e8a750d66e283635d508e68d6c16a14b193ed6 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 16:29:37 -0600 Subject: [PATCH 08/13] Updated CHANGELOG with recent changes. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2534ab0..0dedd20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,10 @@ The git repository is hosted at the following site: * Changed gitignore to ignore the build folder. ### Added +* Added "bacnet property" shell command with list, size, and value options. + The value option can list all the property values of any internal object, + and read or write any specific property values that are writable. + The output is in JSON format. * Added all the basic object property value data types supported for B-ASC sample. (#52) * Added BACnet Application Specific Control (B-ASC) sample. (#51) From b3dc9d43dc97a820c3e67d1f2da64dcd4a02e893 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 21 Nov 2025 17:51:17 -0600 Subject: [PATCH 09/13] [WIP] Refactoring object-property-text to show proprietary and unknown values. --- .../bacnet_shell/bacnet_shell_property.c | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index 411ad4e..c511e27 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -312,6 +312,15 @@ static int cmd_json_print_key_error( return 0; } +static char * +bactext_name(const char *name, unsigned long value, char *buffer, size_t size) +{ + if (name) { + return bacnet_sprintf_to_ascii(buffer, size, "%s", name); + } + return bacnet_ultoa(value, buffer, size); +} + /** * @brief Get or set a BACnet object property value * @param sh Shell @@ -333,20 +342,22 @@ static int cmd_value_print( bool list_value = false; const char *prefix = ""; const char *suffix = ""; - char name[80] = ""; + char property_name[80] = ""; + char class_name[80] = ""; + char code_name[80] = ""; + bactext_name( + bactext_property_name_default(rpdata->object_property, NULL), + rpdata->object_property, property_name, sizeof(property_name)); if (apdu_len < 0) { - if (bactext_property_name_proprietary(rpdata->object_property)) { - cmd_json_print_key_error( - sh, bacnet_ultoa(rpdata->object_property, name, sizeof(name)), - bactext_error_class_name(rpdata->error_class), - bactext_error_code_name(rpdata->error_code), append); - } else { - cmd_json_print_key_error( - sh, bactext_property_name(rpdata->object_property), - bactext_error_class_name(rpdata->error_class), - bactext_error_code_name(rpdata->error_code), append); - } + bactext_name( + bactext_error_class_name_default(rpdata->error_class, NULL), + rpdata->error_class, class_name, sizeof(class_name)); + bactext_name( + bactext_error_code_name_default(rpdata->error_code, NULL), + rpdata->error_code, code_name, sizeof(code_name)); + cmd_json_print_key_error( + sh, property_name, class_name, code_name, append); return 0; } apdu = rpdata->application_data; @@ -356,8 +367,7 @@ static int cmd_value_print( rpdata->array_index); if (len < 0) { cmd_json_print_hex_dump( - sh, prefix, bactext_property_name(object_value.object_property), - apdu, apdu_len, suffix, append); + sh, prefix, property_name, apdu, apdu_len, suffix, append); break; } if (first_value && (len < apdu_len)) { @@ -388,14 +398,10 @@ static int cmd_value_print( char str[str_len + 1]; bacapp_snprintf_value(str, str_len + 1, &object_value); cmd_json_print_key_value( - sh, prefix, - bactext_property_name(object_value.object_property), str, - suffix, append); + sh, prefix, property_name, str, suffix, append); } else { cmd_json_print_hex_dump( - sh, prefix, - bactext_property_name(object_value.object_property), apdu, - len, suffix, append); + sh, prefix, property_name, apdu, len, suffix, append); } } first_value = false; @@ -507,6 +513,7 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) bool null_value = false; bool skip_print = false; bool print_all_properties = false; + char property_name[80] = ""; if ((argc == 2) || (argc == 3)) { err = bacnet_shell_object_type_instance_parse( @@ -602,20 +609,24 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) wpdata.application_data_len = apdu_len; memcpy(&wpdata.application_data, apdu, apdu_len); status = Device_Write_Property(&wpdata); + bactext_name( + bactext_property_name_default(wpdata.object_property, NULL), + wpdata.object_property, property_name, + sizeof(property_name)); if (status) { cmd_json_print_key_error( - sh, bactext_property_name(wpdata.object_property), + sh, property_name, bactext_error_class_name(ERROR_CLASS_PROPERTY), bactext_error_code_name(ERROR_CODE_SUCCESS), ""); } else { cmd_json_print_key_error( - sh, bactext_property_name(wpdata.object_property), + sh, property_name, bactext_error_class_name(wpdata.error_class), bactext_error_code_name(wpdata.error_code), ""); } } else { cmd_json_print_key_error( - sh, bactext_property_name(object_property), + sh, property_name, bactext_error_class_name(ERROR_CLASS_PROPERTY), bactext_error_code_name(ERROR_CODE_UNEXPECTED_DATA), ""); } @@ -638,6 +649,7 @@ static int cmd_list(const struct shell *sh, size_t argc, char **argv) uint32_t object_instance = 0; struct special_property_list_t pPropertyList = { 0 }; unsigned count = 0, index = 0, counter = 0; + char property_name[80] = ""; int err; if ((argc == 2) || (argc == 3)) { @@ -664,19 +676,25 @@ static int cmd_list(const struct shell *sh, size_t argc, char **argv) index = 0; while (pPropertyList.Required.pList[index] != -1) { counter++; + bactext_name( + bactext_property_name_default( + pPropertyList.Required.pList[index], NULL), + pPropertyList.Required.pList[index], property_name, + sizeof(property_name)); shell_print( - sh, "\"%s\"%s", - bactext_property_name(pPropertyList.Required.pList[index]), - (counter == count) ? "]," : ","); + sh, "\"%s\"%s", property_name, (counter == count) ? "]," : ","); index++; } index = 0; while (pPropertyList.Optional.pList[index] != -1) { counter++; + bactext_name( + bactext_property_name_default( + pPropertyList.Optional.pList[index], NULL), + pPropertyList.Optional.pList[index], property_name, + sizeof(property_name)); shell_print( - sh, "\"%s\"%s", - bactext_property_name(pPropertyList.Required.pList[index]), - (counter == count) ? "]," : ","); + sh, "\"%s\"%s", property_name, (counter == count) ? "]," : ","); index++; } index = 0; From f3e511d1277fc70a15d513b9cc57063f2af4d9d8 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Sat, 22 Nov 2025 09:06:53 -0600 Subject: [PATCH 10/13] [WIP] Fixed shell property value when printing all the optional and proprietary values. --- zephyr/subsys/bacnet_shell/bacnet_shell_property.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index c511e27..b7cc13b 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -462,7 +462,7 @@ cmd_print_value_all(const struct shell *sh, BACNET_READ_PROPERTY_DATA *rpdata) index = 0; while (pPropertyList.Optional.pList[index] != -1) { counter++; - rpdata->object_property = pPropertyList.Required.pList[index]; + rpdata->object_property = pPropertyList.Optional.pList[index]; apdu_len = Device_Read_Property(rpdata); if (counter == count) { append = "],"; @@ -474,7 +474,7 @@ cmd_print_value_all(const struct shell *sh, BACNET_READ_PROPERTY_DATA *rpdata) index = 0; while (pPropertyList.Proprietary.pList[index] != -1) { counter++; - rpdata->object_property = pPropertyList.Required.pList[index]; + rpdata->object_property = pPropertyList.Proprietary.pList[index]; apdu_len = Device_Read_Property(rpdata); if (counter == count) { append = "],"; From d2c64e333c9b76e6153d34f81bf9bde0fa7b555d Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Sat, 22 Nov 2025 09:58:10 -0600 Subject: [PATCH 11/13] [WIP] Fixed shell property value when printing any values with zero size (empty). --- zephyr/subsys/bacnet_shell/bacnet_shell_property.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index b7cc13b..fe19b43 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -370,7 +370,7 @@ static int cmd_value_print( sh, prefix, property_name, apdu, apdu_len, suffix, append); break; } - if (first_value && (len < apdu_len)) { + if (first_value && (len > 0) && (len < apdu_len)) { list_value = true; } if (len >= apdu_len) { @@ -408,9 +408,13 @@ static int cmd_value_print( if (last_value) { break; } - apdu_len -= len; - if (apdu) { - apdu += len; + if (len > 0) { + apdu_len -= len; + if (apdu) { + apdu += len; + } + } else { + break; } } From e3f39776f55151d5b8cb9edbd674db4aa4067e61 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 2 Dec 2025 09:08:56 -0600 Subject: [PATCH 12/13] Fixed bacstr API name change. --- zephyr/subsys/bacnet_shell/bacnet_shell_property.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index fe19b43..93118fd 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -316,7 +316,7 @@ static char * bactext_name(const char *name, unsigned long value, char *buffer, size_t size) { if (name) { - return bacnet_sprintf_to_ascii(buffer, size, "%s", name); + return bacnet_snprintf_to_ascii(buffer, size, "%s", name); } return bacnet_ultoa(value, buffer, size); } From 57c3867f62eb6e1dc3a957eeed00f693eeeeb2e9 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Tue, 2 Dec 2025 17:06:17 -0600 Subject: [PATCH 13/13] Added better debug to bacnet property value read and write. Fixed the array element option. --- .../bacnet_shell/bacnet_shell_property.c | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c index 93118fd..971698f 100644 --- a/zephyr/subsys/bacnet_shell/bacnet_shell_property.c +++ b/zephyr/subsys/bacnet_shell/bacnet_shell_property.c @@ -117,10 +117,9 @@ int bacnet_shell_property_parse( } err = bacnet_shell_object_type_instance_parse( sh, argc, argv, object_type, object_instance); - /* property can have [] to denote array */ if (isalpha(argv[3][0])) { - /* choose a property by name */ - scan_count = sscanf(argv[3], "%79s[%u]", name, &array_value); + /* choose a property by name with optional [] to denote array */ + scan_count = sscanf(argv[3], "%79[^[][%u]", name, &array_value); if (scan_count < 1) { shell_error(sh, "parse: missing property: %s.", argv[3]); return -EINVAL; @@ -312,6 +311,27 @@ static int cmd_json_print_key_error( return 0; } +static int cmd_json_print_property_value_tag( + const struct shell *sh, + const char *property, + unsigned long array_index, + unsigned long priority, + unsigned long value_tag, + const char *value_string, + bool success, + const char *append) +{ + shell_print( + sh, + "{\"%s\": {\"array-index\": %lu}, {\"priority\": %lu}, " + "{\"value-tag\": \"%s\"}, {\"value\": \"%s\"}, {\"success\": %s}}%s", + property, array_index, priority, + bactext_application_tag_name(value_tag), value_string, + success ? "true" : "false", append); + + return 0; +} + static char * bactext_name(const char *name, unsigned long value, char *buffer, size_t size) { @@ -598,13 +618,10 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) status = bacapp_parse_application_data(value.tag, value_string, &value); } -#ifdef BACNET_SHELL_PROPERTY_DEBUG - shell_print( - sh, "Parsed %s-%u %s %s -> tag=%u %s", - bactext_object_type_name(object_type), object_instance, - bactext_property_name(object_property), value_string, value.tag, - status ? "successfully" : "unsuccessfully"); -#endif + /* convert the property identifier into a string */ + bactext_name( + bactext_property_name_default(object_property, NULL), + object_property, property_name, sizeof(property_name)); if (status) { apdu_len = bacapp_encode_data(apdu, &value); if (apdu_len) { @@ -613,15 +630,10 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) wpdata.application_data_len = apdu_len; memcpy(&wpdata.application_data, apdu, apdu_len); status = Device_Write_Property(&wpdata); - bactext_name( - bactext_property_name_default(wpdata.object_property, NULL), - wpdata.object_property, property_name, - sizeof(property_name)); if (status) { - cmd_json_print_key_error( - sh, property_name, - bactext_error_class_name(ERROR_CLASS_PROPERTY), - bactext_error_code_name(ERROR_CODE_SUCCESS), ""); + cmd_json_print_property_value_tag( + sh, property_name, array_index, priority, value.tag, + value_string, status, ""); } else { cmd_json_print_key_error( sh, property_name, @@ -634,6 +646,10 @@ static int cmd_value(const struct shell *sh, size_t argc, char **argv) bactext_error_class_name(ERROR_CLASS_PROPERTY), bactext_error_code_name(ERROR_CODE_UNEXPECTED_DATA), ""); } + } else { + cmd_json_print_property_value_tag( + sh, property_name, array_index, priority, value.tag, + value_string, status, ""); } }