diff --git a/CHANGELOG.md b/CHANGELOG.md index 370d8a4e..1ac4b8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,9 @@ The git repositories are hosted at the following sites: ### Added +* Added parsing for property name and optional array in ReadPropertyMultiple + example application. Added the priority value when printing special-event + property types, specificially for exception-schedule property. (#1291) * Added parsing for BACnetSpecialEvent in bacapp for use in apps/writeproperty and add unit tests. (#1290) * Added multi-device support for BACnet gateway routing. Expanded Object_List diff --git a/apps/readpropm/main.c b/apps/readpropm/main.c index ec1c4046..15031323 100644 --- a/apps/readpropm/main.c +++ b/apps/readpropm/main.c @@ -11,6 +11,7 @@ #include #include #include +#include #include /* for time */ #if (__STDC_VERSION__ >= 199901L) && defined(__STDC_ISO_10646__) #include @@ -337,6 +338,76 @@ static void print_help(const char *filename) filename, filename); } +/** + * @brief Parse a BACnet property array string. + * The property array string can be either a property name or a + * property number, with an optional array index in square brackets. + * For example, "present-value" or "85" would parse the Present Value + * property with no array index, while "priority-array[3]" or "87[3]" + * would parse the Priority Array property with an array index of 3. + * + * @param argv [in] The property array string to parse. + * @param property_id [out] The parsed property ID, if the string is valid. + * @param array_index [out] The parsed array index, or 0 if no array index is + * specified. + * @return true if the string was successfully parsed, false if it was invalid + */ +static bool bacnet_property_array_parse( + char *argv, uint32_t *property_id, uint32_t *array_index) +{ + long unsigned int unsigned_value = 0; + unsigned int array_value = 0; + uint32_t found_index = 0; + char name[80] = ""; + int scan_count = 0; + + if (isalpha(argv[0])) { + /* choose a property by name with optional [] to denote array */ + scan_count = sscanf(argv, "%79[^[][%u]", name, &array_value); + if (scan_count < 1) { + fprintf(stderr, "parse: missing property: %s.", argv); + return false; + } + if (!bactext_property_strtol(name, &found_index)) { + fprintf(stderr, "parse: invalid property name: %s.", argv); + return false; + } + if (property_id) { + *property_id = found_index; + } + + } else { + /* choose a property by number */ + scan_count = sscanf(argv, "%lu[%u]", &unsigned_value, &array_value); + if (scan_count < 1) { + fprintf(stderr, "parse: missing property: %s.", argv); + return false; + } + if (unsigned_value > UINT32_MAX) { + fprintf( + stderr, "parse: Invalid property: %s. Must be 0-%u.", argv, + UINT32_MAX); + return false; + } + if (property_id) { + *property_id = (uint32_t)unsigned_value; + } + } + if (scan_count >= 2) { + if (array_value > UINT32_MAX) { + fprintf( + stderr, "parse: Invalid array index: %s. Must be 0-%u.", argv, + UINT32_MAX); + return false; + } + if (array_index) { + *array_index = (uint32_t)array_value; + } + } + + return true; +} + int main(int argc, char *argv[]) { BACNET_ADDRESS src = { 0 }; /* address where message came from */ @@ -353,9 +424,8 @@ int main(int argc, char *argv[]) BACNET_READ_ACCESS_DATA *rpm_object = NULL; BACNET_PROPERTY_REFERENCE *rpm_property = NULL; char *property_token = NULL; - unsigned property_id = 0; - unsigned property_array_index = 0; - int scan_count = 0; + uint32_t property_id = 0; + uint32_t property_array_index = 0; int argi = 0; long dnet = -1; unsigned object_type = 0; @@ -460,27 +530,26 @@ int main(int argc, char *argv[]) property_token = strtok(argv[argi], ","); /* add all the properties and optional index to our list */ while (rpm_property) { - scan_count = sscanf( - property_token, "%u[%u]", &property_id, - &property_array_index); - if (scan_count > 0) { - rpm_property->propertyIdentifier = property_id; - if (rpm_property->propertyIdentifier > - MAX_BACNET_PROPERTY_ID) { - fprintf( - stderr, - "property=%u - it must be less than %u\n", - rpm_property->propertyIdentifier, - MAX_BACNET_PROPERTY_ID + 1); - return 1; - } + property_array_index = BACNET_ARRAY_ALL; + if (!bacnet_property_array_parse( + property_token, &property_id, + &property_array_index)) { + fprintf( + stderr, "Error: property=%s invalid\n", + property_token); + return 1; } - if (scan_count > 1) { - rpm_property->propertyArrayIndex = - property_array_index; - } else { - rpm_property->propertyArrayIndex = BACNET_ARRAY_ALL; + rpm_property->propertyIdentifier = property_id; + if (rpm_property->propertyIdentifier > + MAX_BACNET_PROPERTY_ID) { + fprintf( + stderr, + "property=%u - it must be less than %u\n", + rpm_property->propertyIdentifier, + MAX_BACNET_PROPERTY_ID + 1); + return 1; } + rpm_property->propertyArrayIndex = property_array_index; /* is there another property? */ property_token = strtok(NULL, ","); if (property_token) { diff --git a/apps/writeprop/main.c b/apps/writeprop/main.c index 9e981dcb..f0fc3a7a 100644 --- a/apps/writeprop/main.c +++ b/apps/writeprop/main.c @@ -401,7 +401,8 @@ int main(int argc, char *argv[]) */ fprintf( stderr, - "Error: unable to parse the tag value\n"); + "Error: unable to parse tag=%ld value=%s\n", + property_tag, value_string); return 1; } } else { diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index 6e11c767..124d4a26 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -3613,7 +3613,7 @@ static int bacapp_snprintf_special_event( } slen = bacapp_snprintf_daily_schedule(str, str_len, &value->timeValues); ret_val += bacapp_snprintf_shift(slen, &str, &str_len); - slen = bacapp_snprintf(str, str_len, "}"); + slen = bacapp_snprintf(str, str_len, ",%u}", (unsigned)value->priority); ret_val += bacapp_snprintf_shift(slen, &str, &str_len); return ret_val; diff --git a/test/bacnet/bacapp/src/main.c b/test/bacnet/bacapp/src/main.c index 76af4fd5..4adf29c0 100644 --- a/test/bacnet/bacapp/src/main.c +++ b/test/bacnet/bacapp/src/main.c @@ -1282,6 +1282,44 @@ static void testBACnetApplicationData(void) bacapp_encode_application_data(apdu_special, &value), special_len, NULL); } + /* Test: date entry with two numeric time-value pairs (from header comment + * example). Verifies that sched_str is "{12:30:00.00,100,14:00:00.00,50}" + * and not just the first token "12:30:00.00". */ + char special_event_date_multi[] = + "2023/10/24,{12:30:00.00,100,14:00:00.00,50},5"; + status = bacapp_parse_application_data( + BACNET_APPLICATION_TAG_SPECIAL_EVENT, special_event_date_multi, &value); + zassert_true(status, NULL); + zassert_equal( + value.type.Special_Event.periodTag, + BACNET_SPECIAL_EVENT_PERIOD_CALENDAR_ENTRY, NULL); + zassert_equal( + value.type.Special_Event.period.calendarEntry.tag, BACNET_CALENDAR_DATE, + NULL); + zassert_equal(value.type.Special_Event.timeValues.TV_Count, 2, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[0].Time.hour, 12, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[0].Time.min, 30, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[0].Value.tag, + BACNET_APPLICATION_TAG_UNSIGNED_INT, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[0] + .Value.type.Unsigned_Int, + 100, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[1].Time.hour, 14, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[1].Value.tag, + BACNET_APPLICATION_TAG_UNSIGNED_INT, NULL); + zassert_equal( + value.type.Special_Event.timeValues.Time_Values[1] + .Value.type.Unsigned_Int, + 50, NULL); + zassert_equal(value.type.Special_Event.priority, 5, NULL); + verifyBACnetComplexDataValue( + &value, OBJECT_SCHEDULE, PROP_EXCEPTION_SCHEDULE); return; }