From 7e3e44df24642a430b61dac9fa4cab74c5bc6e0f Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Mon, 2 Mar 2026 16:31:09 -0800 Subject: [PATCH 1/2] Refactor repeated field handling in protobuf utilities --- contact/ui/control_ui.py | 9 ++++++++- contact/utilities/config_io.py | 13 ++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/contact/ui/control_ui.py b/contact/ui/control_ui.py index d4cf1fe..c42820b 100644 --- a/contact/ui/control_ui.py +++ b/contact/ui/control_ui.py @@ -56,6 +56,13 @@ config_folder = os.path.abspath(config.node_configs_file_path) # Load translations field_mapping, help_text = parse_ini_file(translation_file) +def _is_repeated_field(field_desc) -> bool: + """Return True if the protobuf field is repeated. + Protobuf 6.31.0 and later use an is_repeated property, while older versions compare against the label field. + """ + if hasattr(field_desc, "is_repeated"): + return bool(field_desc.is_repeated) + return field_desc.label == field_desc.LABEL_REPEATED def reload_translations() -> None: global translation_file, field_mapping, help_text @@ -564,7 +571,7 @@ def settings_menu(stdscr: object, interface: object) -> None: new_value = new_value == "True" or new_value is True menu_state.start_index.pop() - elif field.label == field.LABEL_REPEATED: # Handle repeated field - Not currently used + elif _is_repeated_field(field): # Handle repeated field - Not currently used new_value = get_repeated_input(current_value) new_value = current_value if new_value is None else new_value.split(", ") menu_state.start_index.pop() diff --git a/contact/utilities/config_io.py b/contact/utilities/config_io.py index ca1d4df..35c366c 100644 --- a/contact/utilities/config_io.py +++ b/contact/utilities/config_io.py @@ -9,6 +9,17 @@ from meshtastic.util import camel_to_snake, snake_to_camel, fromStr # defs are from meshtastic/python/main +def _is_repeated_field(field_desc) -> bool: + """Return True if the protobuf field is repeated. + + Protobuf 6.31.0+ exposes `is_repeated`, while older versions require + checking `label == LABEL_REPEATED`. + """ + if hasattr(field_desc, "is_repeated"): + return bool(field_desc.is_repeated) + return field_desc.label == field_desc.LABEL_REPEATED + + def traverseConfig(config_root, config, interface_config) -> bool: """Iterate through current config level preferences and either traverse deeper if preference is a dict or set preference""" snake_name = camel_to_snake(config_root) @@ -89,7 +100,7 @@ def setPref(config, comp_name, raw_val) -> bool: return False # repeating fields need to be handled with append, not setattr - if pref.label != pref.LABEL_REPEATED: + if not _is_repeated_field(pref): try: if config_type.message_type is not None: config_values = getattr(config_part, config_type.name) From 6af1c46bd39ed80870d04bf481b94f8c409bf90c Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Mon, 2 Mar 2026 16:31:33 -0800 Subject: [PATCH 2/2] bump version to 1.4.17 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 83a4d3e..50238d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "contact" -version = "1.4.16" +version = "1.4.17" description = "This Python curses client for Meshtastic is a terminal-based client designed to manage device settings, enable mesh chat communication, and handle configuration backups and restores." authors = [ {name = "Ben Lipsey",email = "ben@pdxlocations.com"}