diff --git a/contact/localisations/en.ini b/contact/localisations/en.ini index 9d803bc..ff7a283 100644 --- a/contact/localisations/en.ini +++ b/contact/localisations/en.ini @@ -361,6 +361,7 @@ override_console_serial_port, "Override console serial port", "If set to true, t [module.external_notification] title, "External Notification" +ringtone, "Ringtone", "RTTTL ringtone played by supported external-notification hardware." enabled, "Enabled", "Enables the module." output_ms, "Length", "Specifies how long in milliseconds you would like your GPIOs to be active. In case of the repeat option, this is the duration of every tone and pause." output, "Output GPIO", "Define the output pin GPIO setting Defaults to EXT_NOTIFY_OUT if set for the board. In standalone devices this pin should drive the LED to match the UI." @@ -411,6 +412,7 @@ device_telemetry_enabled, "Device telemetry enabled", "Enable the Device Telemet [module.canned_message] title, "Canned Message" +messages, "Messages", "Pipe-separated canned messages, for example: Hi|On my way|Yes|No." rotary1_enabled, "Rotary encoder enabled", "Enable the default rotary encoder." inputbroker_pin_a, "Input broker pin A", "GPIO Pin Value (1-39) For encoder port A." inputbroker_pin_b, "Input broker pin B", "GPIO Pin Value (1-39) For encoder port B." diff --git a/contact/ui/control_ui.py b/contact/ui/control_ui.py index 7dfce06..1eb1f14 100644 --- a/contact/ui/control_ui.py +++ b/contact/ui/control_ui.py @@ -609,6 +609,24 @@ def settings_menu(stdscr: object, interface: object) -> None: # Fetch human-readable name from field_mapping human_readable_name = field_mapping.get(full_key, selected_option) + if field is None and selected_option in ("ringtone", "messages"): + is_ringtone = selected_option == "ringtone" + getter_name = "get_ringtone" if is_ringtone else "get_canned_message" + setter_name = "set_ringtone" if is_ringtone else "set_canned_message" + getter = getattr(interface.localNode, getter_name, None) + setter = getattr(interface.localNode, setter_name, None) + fetched_value = getter() if callable(getter) else None + current_value = fetched_value if fetched_value is not None else current_value + new_value = get_text_input( + f"{human_readable_name} is currently: {current_value}", selected_option, None + ) + if new_value is not None and new_value != current_value and callable(setter): + setter(new_value) + menu_state.current_menu[selected_option] = (field, new_value) + menu_state.start_index.pop() + menu_state.need_redraw = True + continue + if selected_option in ["longName", "shortName", "isLicensed"]: if selected_option in ["longName", "shortName"]: new_value = get_text_input( diff --git a/contact/ui/menus.py b/contact/ui/menus.py index e266e3e..619374a 100644 --- a/contact/ui/menus.py +++ b/contact/ui/menus.py @@ -118,7 +118,19 @@ def generate_menu_from_protobuf(interface: object) -> Dict[str, Any]: # Add Module Settings module = module_config_pb2.ModuleConfig() current_module_config = interface.localNode.moduleConfig if interface else None - menu_structure["Main Menu"]["Module Settings"] = extract_fields(module, current_module_config) + module_settings = extract_fields(module, current_module_config) + if interface and interface.localNode: + # These values use dedicated admin requests rather than ModuleConfig + # fields, so expose them alongside their related module settings. + module_settings.setdefault("external_notification", {})["ringtone"] = ( + None, + getattr(interface.localNode, "ringtone", "") or "", + ) + module_settings.setdefault("canned_message", {})["messages"] = ( + None, + getattr(interface.localNode, "cannedPluginMessage", "") or "", + ) + menu_structure["Main Menu"]["Module Settings"] = module_settings # Add App Settings menu_structure["Main Menu"]["App Settings"] = {"Open": "app_settings"} diff --git a/tests/test_menus.py b/tests/test_menus.py index 4ae95c9..7c11094 100644 --- a/tests/test_menus.py +++ b/tests/test_menus.py @@ -26,3 +26,21 @@ class MenusTests(unittest.TestCase): self.assertLess(keys.index("Factory Reset"), keys.index("factory_reset_config")) self.assertEqual(keys[keys.index("Factory Reset") + 1], "factory_reset_config") + + def test_module_settings_include_ringtone_and_canned_messages(self) -> None: + local_node = SimpleNamespace( + localConfig=config_pb2.Config(), + moduleConfig=module_config_pb2.ModuleConfig(), + ringtone="tone", + cannedPluginMessage="Hi|Bye", + getChannelByChannelIndex=lambda _: None, + ) + interface = SimpleNamespace( + localNode=local_node, + getMyNodeInfo=lambda: {"position": {"latitude": 0.0, "longitude": 0.0, "altitude": 0}}, + ) + + module_settings = generate_menu_from_protobuf(interface)["Main Menu"]["Module Settings"] + + self.assertEqual(module_settings["external_notification"]["ringtone"], (None, "tone")) + self.assertEqual(module_settings["canned_message"]["messages"], (None, "Hi|Bye"))