diff --git a/apps/gtk-discover/main.c b/apps/gtk-discover/main.c index a2ed7a97..c4bda8d2 100644 --- a/apps/gtk-discover/main.c +++ b/apps/gtk-discover/main.c @@ -78,7 +78,12 @@ enum { }; enum { + PROPERTY_COL_DEVICE_ID, + PROPERTY_COL_OBJECT_TYPE, + PROPERTY_COL_OBJECT_ID, PROPERTY_COL_ID, + PROPERTY_COL_ARRAY_INDEX, + PROPERTY_COL_VALUE_TAG, PROPERTY_COL_NAME, PROPERTY_COL_VALUE, PROPERTY_NUM_COLS @@ -254,6 +259,7 @@ static void add_discovered_properties_to_gui( GtkTreeIter iter; unsigned int property_count = 0; unsigned int index = 0; + uint32_t array_index = BACNET_ARRAY_ALL; uint32_t property_id = 0; BACNET_OBJECT_PROPERTY_VALUE object_value = { 0 }; BACNET_APPLICATION_DATA_VALUE value = { 0 }; @@ -279,25 +285,31 @@ static void add_discovered_properties_to_gui( object_value.object_type = object_type; object_value.object_instance = object_instance; object_value.object_property = property_id; - object_value.array_index = BACNET_ARRAY_ALL; + object_value.array_index = array_index; object_value.value = &value; 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); gtk_list_store_set( - property_store, &iter, PROPERTY_COL_ID, property_id, - PROPERTY_COL_NAME, property_string, PROPERTY_COL_VALUE, str, - -1); + property_store, &iter, PROPERTY_COL_DEVICE_ID, device_id, + PROPERTY_COL_OBJECT_TYPE, object_type, + PROPERTY_COL_OBJECT_ID, object_instance, PROPERTY_COL_ID, + property_id, PROPERTY_COL_ARRAY_INDEX, array_index, + PROPERTY_COL_VALUE_TAG, value.tag, PROPERTY_COL_NAME, + property_string, PROPERTY_COL_VALUE, str, -1); } else { status = false; } } if (!status) { gtk_list_store_set( - property_store, &iter, PROPERTY_COL_ID, property_id, - PROPERTY_COL_NAME, property_string, PROPERTY_COL_VALUE, "-", - -1); + property_store, &iter, PROPERTY_COL_DEVICE_ID, device_id, + PROPERTY_COL_OBJECT_TYPE, object_type, PROPERTY_COL_OBJECT_ID, + object_instance, PROPERTY_COL_ID, property_id, + PROPERTY_COL_ARRAY_INDEX, array_index, PROPERTY_COL_VALUE_TAG, + value.tag, PROPERTY_COL_NAME, property_string, + PROPERTY_COL_VALUE, "-", -1); } } } @@ -354,6 +366,98 @@ static void on_discover_devices_clicked(GtkButton *button, gpointer data) Send_WhoIs_Global(0, 4194303); } +static void on_property_edited( + GtkCellRendererText *renderer, + gchar *path_string, + gchar *new_text, + gpointer user_data) +{ + GtkTreeModel *model = GTK_TREE_MODEL(user_data); + GtkTreeIter iter = { 0 }; + guint device_id = 0; + guint object_type = 0; + guint object_id = 0; + guint property_id = 0; + guint array_index = 0; + long priority = BACNET_NO_PRIORITY; + guint value_tag = 0; + unsigned enumerated_value = 0; + bool status = false; + uint8_t invoke_id; + bool null_value = false; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + + (void)renderer; /* unused parameter */ + if (gtk_tree_model_get_iter_from_string(model, &iter, path_string)) { + gtk_tree_model_get( + model, &iter, PROPERTY_COL_DEVICE_ID, &device_id, -1); + gtk_tree_model_get( + model, &iter, PROPERTY_COL_OBJECT_TYPE, &object_type, -1); + gtk_tree_model_get( + model, &iter, PROPERTY_COL_OBJECT_ID, &object_id, -1); + gtk_tree_model_get( + model, &iter, PROPERTY_COL_ARRAY_INDEX, &array_index, -1); + gtk_tree_model_get( + model, &iter, PROPERTY_COL_VALUE_TAG, &value_tag, -1); + gtk_tree_model_get(model, &iter, PROPERTY_COL_ID, &property_id, -1); + /* allow for optional priority using @ symbol for commandables */ + if (property_list_commandable_member(object_type, property_id)) { + /* search the new_text for the @ symbol */ + char *at_ptr = strchr(new_text, '@'); + 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(new_text, "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)property_id, new_text, &enumerated_value); + if (status) { + value.tag = BACNET_APPLICATION_TAG_ENUMERATED; + value.type.Enumerated = (uint32_t)enumerated_value; + } + } else { + status = bacapp_parse_application_data(value_tag, new_text, &value); + } + printf( + "Parsed %s-%u %s %s -> tag=%u %s\n", + bactext_object_type_name(object_type), object_id, + bactext_property_name(property_id), new_text, value_tag, + status ? "successfully" : "unsuccessfully"); + if (status) { + invoke_id = Send_Write_Property_Request( + device_id, object_type, object_id, property_id, &value, + priority, array_index); + if (invoke_id) { + printf( + "WriteProperty to Device %u %s-%u %s = %s\n", device_id, + bactext_object_type_name(object_type), object_id, + bactext_property_name(property_id), new_text); + gtk_list_store_set( + property_store, &iter, PROPERTY_COL_VALUE, new_text, -1); + } + } + } +} + /** * @brief Process discovered devices and add them to the GUI */ @@ -507,9 +611,14 @@ static void setup_property_tree_view(void) /* Create list store */ property_store = gtk_list_store_new( - PROPERTY_NUM_COLS, G_TYPE_UINT, /* Property ID */ - G_TYPE_STRING, /* Property Name */ - G_TYPE_STRING); /* Property Value */ + PROPERTY_NUM_COLS, G_TYPE_UINT, /* PROPERTY_COL_DEVICE_ID */ + G_TYPE_UINT, /* PROPERTY_COL_OBJECT_TYPE */ + G_TYPE_UINT, /* PROPERTY_COL_OBJECT_ID */ + G_TYPE_UINT, /* PROPERTY_COL_ID */ + G_TYPE_UINT, /* PROPERTY_COL_ARRAY_INDEX */ + G_TYPE_INT, /* PROPERTY_COL_VALUE_TAG */ + G_TYPE_STRING, /* PROPERTY_COL_NAME */ + G_TYPE_STRING); /* PROPERTY_COL_VALUE */ /* Create tree view */ property_tree_view = @@ -526,6 +635,10 @@ static void setup_property_tree_view(void) renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( "Value", renderer, "text", PROPERTY_COL_VALUE, NULL); + g_object_set(renderer, "editable", TRUE, NULL); + g_signal_connect( + renderer, "edited", G_CALLBACK(on_property_edited), + GTK_TREE_MODEL(property_store)); gtk_tree_view_append_column(GTK_TREE_VIEW(property_tree_view), column); } diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index 2c940230..3b2e469c 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -14,29 +14,47 @@ static const char *ASHRAE_Reserved_String = "Reserved for Use by ASHRAE"; static const char *Vendor_Proprietary_String = "Vendor Proprietary Value"; -/* Search for a text value first based on the corresponding text list, then by - * attempting to convert to an integer value. */ -static bool bactext_strtol_index( - INDTEXT_DATA *istring, const char *search_name, unsigned *found_index) +/** + * @brief Attempt to convert a numeric string into a unsigned long integer + * @param search_name - string to convert + * @param found_index - where to put the converted value + * @return true if converted and found_index is set + * @return false if not converted and found_index is not set + */ +bool bactext_strtoul(const char *search_name, unsigned *found_index) { char *endptr; - long value; + unsigned long value; + value = strtoul(search_name, &endptr, 0); + if (endptr == search_name) { + /* No digits found */ + return false; + } + if (value == ULONG_MAX) { + /* If the correct value is outside the range of representable values, + {ULONG_MAX} shall be returned */ + return false; + } + if (*endptr != '\0') { + /* Extra text found */ + return false; + } + *found_index = (unsigned)value; + + return true; +} + +/* Search for a text value first based on the corresponding text list, then by + * attempting to convert to an integer value. */ +static bool bactext_strtoul_index( + INDTEXT_DATA *istring, const char *search_name, unsigned *found_index) +{ if (indtext_by_istring(istring, search_name, found_index) == true) { return true; - } else { - value = strtol(search_name, &endptr, 0); - if (endptr == search_name) { - /* No digits found */ - return false; - } else if (*endptr != '\0') { - /* Extra text found */ - return false; - } else { - *found_index = (unsigned)value; - return true; - } } + + return bactext_strtoul(search_name, found_index); } INDTEXT_DATA bacnet_confirmed_service_names[] = { @@ -252,7 +270,7 @@ bool bactext_object_type_index(const char *search_name, unsigned *found_index) bool bactext_object_type_strtol(const char *search_name, unsigned *found_index) { - return bactext_strtol_index( + return bactext_strtoul_index( bacnet_object_type_names, search_name, found_index); } @@ -850,7 +868,7 @@ bool bactext_property_index(const char *search_name, unsigned *found_index) bool bactext_property_strtol(const char *search_name, unsigned *found_index) { - return bactext_strtol_index( + return bactext_strtoul_index( bacnet_property_names, search_name, found_index); } @@ -1816,7 +1834,7 @@ bool bactext_event_state_index(const char *search_name, unsigned *found_index) bool bactext_event_state_strtol(const char *search_name, unsigned *found_index) { - return bactext_strtol_index( + return bactext_strtoul_index( bacnet_event_state_names, search_name, found_index); } @@ -2082,7 +2100,7 @@ const char *bactext_life_safety_operation_name(unsigned index) } } -INDTEXT_DATA life_safety_state_names[] = { +INDTEXT_DATA bactext_life_safety_state_names[] = { { LIFE_SAFETY_STATE_QUIET, "quiet" }, { LIFE_SAFETY_STATE_PRE_ALARM, "pre-alarm" }, { LIFE_SAFETY_STATE_ALARM, "alarm" }, @@ -2125,7 +2143,7 @@ const char *bactext_life_safety_state_name(unsigned index) { if (index < LIFE_SAFETY_STATE_PROPRIETARY_MIN) { return indtext_by_index_default( - life_safety_state_names, index, ASHRAE_Reserved_String); + bactext_life_safety_state_names, index, ASHRAE_Reserved_String); } else if (index <= LIFE_SAFETY_STATE_PROPRIETARY_MAX) { return Vendor_Proprietary_String; } else { @@ -2153,7 +2171,7 @@ const char *bactext_silenced_state_name(unsigned index) } } -INDTEXT_DATA lighting_in_progress[] = { +INDTEXT_DATA bacnet_lighting_in_progress_names[] = { { BACNET_LIGHTING_IDLE, "idle" }, { BACNET_LIGHTING_FADE_ACTIVE, "fade" }, { BACNET_LIGHTING_RAMP_ACTIVE, "ramp" }, @@ -2167,13 +2185,13 @@ const char *bactext_lighting_in_progress(unsigned index) { if (index < MAX_BACNET_LIGHTING_IN_PROGRESS) { return indtext_by_index_default( - lighting_in_progress, index, ASHRAE_Reserved_String); + bacnet_lighting_in_progress_names, index, ASHRAE_Reserved_String); } else { return "Invalid BACnetLightingInProgress"; } } -INDTEXT_DATA lighting_transition[] = { +INDTEXT_DATA bacnet_lighting_transition_names[] = { { BACNET_LIGHTING_TRANSITION_NONE, "none" }, { BACNET_LIGHTING_TRANSITION_FADE, "fade" }, { BACNET_LIGHTING_TRANSITION_RAMP, "ramp" }, @@ -2184,7 +2202,7 @@ const char *bactext_lighting_transition(unsigned index) { if (index < BACNET_LIGHTING_TRANSITION_PROPRIETARY_MIN) { return indtext_by_index_default( - lighting_transition, index, ASHRAE_Reserved_String); + bacnet_lighting_transition_names, index, ASHRAE_Reserved_String); } else if (index <= BACNET_LIGHTING_TRANSITION_PROPRIETARY_MAX) { return Vendor_Proprietary_String; } else { @@ -2222,7 +2240,7 @@ const char *bactext_lighting_operation_name(unsigned index) bool bactext_lighting_operation_strtol( const char *search_name, unsigned *found_index) { - return bactext_strtol_index( + return bactext_strtoul_index( bacnet_lighting_operation_names, search_name, found_index); } @@ -2252,7 +2270,7 @@ const char *bactext_binary_lighting_pv_name(unsigned index) bool bactext_binary_lighting_pv_names_strtol( const char *search_name, unsigned *found_index) { - return bactext_strtol_index( + return bactext_strtoul_index( bacnet_binary_lighting_pv_names, search_name, found_index); } @@ -2609,3 +2627,150 @@ const char *bactext_program_error_name(unsigned index) return "Invalid BACnetProgramError"; } } + +/** + * @brief For a given enumerated object property string, + * find the enumeration value + * @param object_type - object type identifier + * @param property - object property identifier + * @param search_name - text string for which is searched + * @param found_index - if found, the value is set to this variable + * @return true if the string is found and found_index is set + * @return false if the string is not found and found_index is not set + */ +bool bactext_object_property_strtoul( + BACNET_OBJECT_TYPE object_type, + BACNET_PROPERTY_ID object_property, + const char *search_name, + unsigned *found_index) +{ + bool status = false; + + switch (object_property) { + case PROP_PROPERTY_LIST: + status = bactext_strtoul_index( + bacnet_property_names, search_name, found_index); + break; + case PROP_OBJECT_TYPE: + status = bactext_strtoul_index( + bacnet_object_type_names, search_name, found_index); + break; + case PROP_EVENT_STATE: + status = bactext_strtoul_index( + bacnet_event_state_names, search_name, found_index); + break; + case PROP_UNITS: + status = bactext_strtoul_index( + bacnet_engineering_unit_names, search_name, found_index); + break; + case PROP_POLARITY: + status = bactext_strtoul_index( + bacnet_binary_polarity_names, search_name, found_index); + break; + case PROP_PRESENT_VALUE: + case PROP_RELINQUISH_DEFAULT: + switch (object_type) { + case OBJECT_BINARY_INPUT: + case OBJECT_BINARY_OUTPUT: + case OBJECT_BINARY_VALUE: + status = bactext_strtoul_index( + bacnet_binary_present_value_names, search_name, + found_index); + break; + case OBJECT_BINARY_LIGHTING_OUTPUT: + status = bactext_strtoul_index( + bacnet_binary_lighting_pv_names, search_name, + found_index); + break; + default: + break; + } + break; + case PROP_RELIABILITY: + status = bactext_strtoul_index( + bacnet_reliability_names, search_name, found_index); + break; + case PROP_SYSTEM_STATUS: + status = bactext_strtoul_index( + bacnet_device_status_names, search_name, found_index); + break; + case PROP_SEGMENTATION_SUPPORTED: + status = bactext_strtoul_index( + bacnet_segmentation_names, search_name, found_index); + break; + case PROP_NODE_TYPE: + status = bactext_strtoul_index( + bacnet_node_type_names, search_name, found_index); + break; + case PROP_TRANSITION: + status = bactext_strtoul_index( + bacnet_lighting_transition_names, search_name, found_index); + break; + case PROP_IN_PROGRESS: + status = bactext_strtoul_index( + bacnet_lighting_in_progress_names, search_name, found_index); + break; + case PROP_LOGGING_TYPE: + status = bactext_strtoul_index( + bactext_logging_type_names, search_name, found_index); + break; + case PROP_MODE: + case PROP_ACCEPTED_MODES: + status = bactext_strtoul_index( + bactext_life_safety_mode_names, search_name, found_index); + break; + case PROP_OPERATION_EXPECTED: + status = bactext_strtoul_index( + bactext_life_safety_operation_names, search_name, found_index); + break; + case PROP_TRACKING_VALUE: + switch (object_type) { + case OBJECT_LIFE_SAFETY_POINT: + case OBJECT_LIFE_SAFETY_ZONE: + status = bactext_strtoul_index( + bactext_life_safety_state_names, search_name, + found_index); + break; + default: + break; + } + break; + case PROP_PROGRAM_CHANGE: + status = bactext_strtoul_index( + bactext_program_request_names, search_name, found_index); + break; + case PROP_PROGRAM_STATE: + status = bactext_strtoul_index( + bactext_program_state_names, search_name, found_index); + break; + case PROP_REASON_FOR_HALT: + status = bactext_strtoul_index( + bactext_program_error_names, search_name, found_index); + break; + case PROP_NETWORK_NUMBER_QUALITY: + status = bactext_strtoul_index( + bactext_network_number_quality_names, search_name, found_index); + break; + case PROP_NETWORK_TYPE: + status = bactext_strtoul_index( + bactext_network_port_type_names, search_name, found_index); + break; + case PROP_PROTOCOL_LEVEL: + status = bactext_strtoul_index( + bactext_protocol_level_names, search_name, found_index); + break; + case PROP_EVENT_TYPE: + status = bactext_strtoul_index( + bacnet_event_type_names, search_name, found_index); + break; + case PROP_NOTIFY_TYPE: + status = bactext_strtoul_index( + bacnet_notify_type_names, search_name, found_index); + break; + default: + status = bactext_strtoul(search_name, found_index); + break; + } + + return status; +} diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h index 810d3289..180d6c01 100644 --- a/src/bacnet/bactext.h +++ b/src/bacnet/bactext.h @@ -198,6 +198,15 @@ const char *bactext_program_state_name(unsigned index); BACNET_STACK_EXPORT const char *bactext_program_error_name(unsigned index); +BACNET_STACK_EXPORT +bool bactext_strtoul(const char *search_name, unsigned *found_index); +BACNET_STACK_EXPORT +bool bactext_object_property_strtoul( + BACNET_OBJECT_TYPE object_type, + BACNET_PROPERTY_ID object_property, + const char *search_name, + unsigned *found_index); + #ifdef __cplusplus } #endif /* __cplusplus */