mirror of
https://github.com/pdxlocations/contact.git
synced 2026-11-02 09:00:32 +01:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0143d9bacc | ||
|
|
424e10af1e | ||
|
|
986cf817f3 | ||
|
|
c699845c81 |
No files matched your search
@@ -252,8 +252,6 @@ heading_bold, "Heading bold", "The heading can be hard to read when 'INVERTED' o
|
||||
wake_on_tap_or_motion, "Wake on tap or motion", "This option enables the ability to wake the device screen when motion, such as a tap on the device, is detected via an attached accelerometer, or a capacitive touch button."
|
||||
compass_orientation, "Compass orientation", "Whether to rotate the compass."
|
||||
use_12h_clock, "Use 12 hour clock"
|
||||
use_long_node_name, "Use long node name", "Show each node's long name on the device display when space permits."
|
||||
enable_message_bubbles, "Enable message bubbles", "Display messages in chat-style bubbles on supported device displays."
|
||||
|
||||
[config.device_ui]
|
||||
title, "Device UI"
|
||||
@@ -272,10 +270,6 @@ node_filter, "Node Filter", ""
|
||||
node_highlight, "Node Highlight", ""
|
||||
calibration_data, "Calibration Data", ""
|
||||
map_data, "Map Data", ""
|
||||
compass_mode, "Compass mode", "Controls whether the compass is dynamic, fixed, or freezes its heading."
|
||||
screen_rgb_color, "Screen RGB color", "Sets the display color on supported RGB screens."
|
||||
is_clockface_analog, "Analog clock face", "Use an analog clock face on supported device displays."
|
||||
gps_format, "GPS coordinate format", "Select the coordinate format used by the device UI."
|
||||
|
||||
[config.device_ui.node_filter]
|
||||
title, "Node Filter"
|
||||
@@ -301,12 +295,6 @@ home, "Home", ""
|
||||
style, "Style", ""
|
||||
follow_gps, "Follow GPS", ""
|
||||
|
||||
[config.device_ui.map_data.home]
|
||||
title, "Home location", ""
|
||||
zoom, "Zoom", "Map zoom level for the home location."
|
||||
latitude, "Latitude", "Latitude of the home location."
|
||||
longitude, "Longitude", "Longitude of the home location."
|
||||
|
||||
[config.lora]
|
||||
title, "LoRa"
|
||||
use_preset, "Use modem preset", "Presets are pre-defined modem settings (Bandwidth, Spread Factor, and Coding Rate) which influence both message speed and range. The vast majority of users use a preset."
|
||||
@@ -326,8 +314,6 @@ override_frequency, "Override frequency in MHz", "Overrides frequency slot. May
|
||||
pa_fan_disabled, "PA Fan Disabled", "If true, disable the build-in PA FAN using pin define in RF95_FAN_EN"
|
||||
ignore_mqtt, "Ignore MQTT", "Ignores any messages it receives via LoRa that came via MQTT somewhere along the path towards the device."
|
||||
config_ok_to_mqtt, "OK to MQTT", "Indicates that the user approves their packets to be uplinked to MQTT brokers."
|
||||
fem_lna_mode, "FEM LNA mode", "Configures the front-end module low-noise amplifier mode on supported hardware."
|
||||
serial_hal_only, "Serial HAL only", "Use only the serial hardware abstraction layer on supported hardware."
|
||||
|
||||
[config.bluetooth]
|
||||
title, "Bluetooth"
|
||||
@@ -344,7 +330,6 @@ serial_enabled, "Enable serial console", ""
|
||||
debug_log_api_enabled, "Enable debug log", "Set this to true to continue outputting live debug logs over serial or Bluetooth when the API is active."
|
||||
admin_channel_enabled, "Enable legacy admin channel", "If the node you need to administer or be administered by is running 2.4.x or earlier, you should set this to enabled. Requires a secondary channel named 'admin' be present on both nodes."
|
||||
admin_key, "Admin keys", "The public key(s) authorized to send administrative messages to this node. Only messages signed by these keys will be accepted for administrative control. Up to 3."
|
||||
packet_signature_policy, "Packet signature policy", "Controls how the node validates packet signatures."
|
||||
|
||||
[module.mqtt]
|
||||
title, "MQTT"
|
||||
@@ -376,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."
|
||||
@@ -406,7 +392,6 @@ title, "Range Test"
|
||||
enabled, "Enabled", "Enables the module."
|
||||
sender, "Sender interval", "How long to wait between sending sequential test packets in seconds. 0 is default which disables sending messages."
|
||||
save, "Save CSV file", "If enabled, all received messages are saved to the device's flash memory in a file named rangetest.csv. Leave disabled when using the Android or Apple apps. Saves directly to the device's flash memory (without the need for a smartphone). [warning]Only available on ESP32-based devices.[/warning]"
|
||||
clear_on_reboot, "Clear CSV on reboot", "Delete the RangeTest.csv file when the device reboots. [warning]Only available on ESP32-based devices.[/warning]"
|
||||
|
||||
[module.telemetry]
|
||||
title, "Telemetry"
|
||||
@@ -424,10 +409,10 @@ health_measurement_enabled, "Health telemetry interval", "This option is used to
|
||||
health_update_interval, "Health telemetry enabled", "This option is used to enable/disable the sending of health data from an attached supported sensor over the mesh network."
|
||||
health_screen_enabled, "Health screen enabled", "Show the health telemetry data on the device display."
|
||||
device_telemetry_enabled, "Device telemetry enabled", "Enable the Device Telemetry"
|
||||
air_quality_screen_enabled, "Air quality screen enabled", "Show air-quality telemetry on the device display."
|
||||
|
||||
[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."
|
||||
@@ -485,52 +470,5 @@ use_pullup, "Use pull-up", "Whether or not use INPUT_PULLUP mode for GPIO pin. O
|
||||
title, "Paxcounter"
|
||||
enabled, "Enabled", "Enables the module."
|
||||
paxcounter_update_interval, "Update interval", "The interval in seconds of how often we can send a message to the mesh when a state change is detected."
|
||||
wifi_threshold, "Wi-Fi threshold", "Wi-Fi RSSI threshold. Defaults to -80."
|
||||
Wi-Fi_threshold, "Wi-Fi Threshold", "WiFi RSSI threshold. Defaults to -80"
|
||||
ble_threshold, "BLE Threshold", "BLE RSSI threshold. Defaults to -80"
|
||||
|
||||
[module.statusmessage]
|
||||
title, "Status Message"
|
||||
node_status, "Node status", ""
|
||||
|
||||
[module.traffic_management]
|
||||
title, "Traffic Management"
|
||||
position_min_interval_secs, "Position minimum interval", "Minimum seconds between position updates from the same node. Set to 0 to disable position-update suppression."
|
||||
nodeinfo_direct_response_max_hops, "NodeInfo direct-response maximum hops", "Maximum hop distance at which cached NodeInfo requests receive a direct response. Set to 0 to disable direct responses."
|
||||
rate_limit_window_secs, "Rate-limit window", "Per-node rate-limit window in seconds. Set to 0 to disable rate limiting."
|
||||
rate_limit_max_packets, "Rate-limit maximum packets", "Maximum packets allowed per node during the rate-limit window. Set to 0 to disable rate limiting."
|
||||
unknown_packet_threshold, "Unknown-packet threshold", "Maximum unknown or undecryptable packets per rate-limit window before packets from that source are dropped. Set to 0 to disable filtering."
|
||||
|
||||
[module.tak]
|
||||
title, "TaK"
|
||||
team, "Team", "Your team color."
|
||||
role, "Role", "Your role in the team."
|
||||
|
||||
[module.mesh_beacon]
|
||||
title, "Mesh Beacon", ""
|
||||
flags, "Beacon options", "Bitwise combination of the listen, broadcast, and legacy-split options."
|
||||
broadcast_send_as_node, "Broadcast as node", "Optional node ID to use as the sender of beacon packets. Leave unset to send as this node."
|
||||
broadcast_message, "Broadcast message", "Message included in each beacon broadcast. Maximum 100 bytes."
|
||||
broadcast_offer_channel, "Offered channel", "Optional channel name and key to advertise in the beacon offer."
|
||||
broadcast_offer_region, "Offered region", "Optional LoRa region to advertise in the beacon offer."
|
||||
broadcast_offer_preset, "Offered modem preset", "Optional modem preset to advertise in the beacon offer."
|
||||
broadcast_on_channel, "Broadcast channel", "Optional channel settings to use when transmitting beacons. Uses the primary channel when unset."
|
||||
broadcast_on_region, "Broadcast region", "Optional LoRa region to use when transmitting beacons."
|
||||
broadcast_on_preset, "Broadcast modem preset", "Optional modem preset to use when transmitting beacons."
|
||||
broadcast_interval_secs, "Broadcast interval", "How often to broadcast beacons in seconds. The minimum and default is 3600 seconds."
|
||||
broadcast_targets, "Broadcast targets", "Optional list of channel, region, and modem-preset targets for beacon broadcasts."
|
||||
|
||||
[module.mesh_beacon.broadcast_offer_channel]
|
||||
title, "Offered channel", ""
|
||||
psk, "PSK", "Encryption key for the offered channel."
|
||||
name, "Name", "Name of the offered channel."
|
||||
|
||||
[module.mesh_beacon.broadcast_on_channel]
|
||||
title, "Broadcast channel", ""
|
||||
psk, "PSK", "Encryption key for the broadcast channel."
|
||||
name, "Name", "Name of the broadcast channel."
|
||||
|
||||
[module.mesh_beacon.broadcast_targets]
|
||||
title, "Broadcast targets", ""
|
||||
preset, "Modem preset", "Optional modem preset for this beacon target."
|
||||
region, "Region", "LoRa region for this beacon target."
|
||||
channel_index, "Channel index", "Index of the configured channel to use for this beacon target."
|
||||
@@ -1675,21 +1675,25 @@ def remove_notification(channel_number: int) -> None:
|
||||
|
||||
def draw_text_field(win: curses.window, text: str, color: int) -> None:
|
||||
win.border()
|
||||
height, width = win.getmaxyx()
|
||||
|
||||
# Put a small hint in the border of the message entry field.
|
||||
# We key off the "Message:" prompt to avoid affecting other bordered fields.
|
||||
if isinstance(text, str) and text.startswith("Message:"):
|
||||
hint = " Ctrl+K Help "
|
||||
h, w = win.getmaxyx()
|
||||
x = max(2, w - len(hint) - 2)
|
||||
x = max(2, width - len(hint) - 2)
|
||||
try:
|
||||
win.addstr(0, x, hint, get_color("commands"))
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# Draw the actual field text
|
||||
# Clear the row before redrawing. Reply context can be longer than the
|
||||
# next value (or empty after Ctrl+R cancels it), so simply overwriting the
|
||||
# new text leaves trailing characters from the old context on screen.
|
||||
try:
|
||||
win.addstr(1, 1, text, color)
|
||||
if height > 2 and width > 2:
|
||||
win.addstr(1, 1, " " * (width - 2), color)
|
||||
win.addstr(1, 1, text[: max(0, width - 2)], color)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
+13
-1
@@ -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"}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "contact"
|
||||
version = "1.5.12"
|
||||
version = "1.5.13"
|
||||
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"}
|
||||
|
||||
@@ -152,6 +152,15 @@ class ContactUiTests(unittest.TestCase):
|
||||
self.assertEqual(ui_state.reply_context, "")
|
||||
self.assertFalse(ui_state.reply_id_unavailable)
|
||||
|
||||
def test_draw_text_field_clears_stale_reply_context(self) -> None:
|
||||
win = mock.Mock()
|
||||
win.getmaxyx.return_value = (3, 30)
|
||||
|
||||
contact_ui.draw_text_field(win, "Message: ", 1)
|
||||
|
||||
self.assertIn(mock.call(1, 1, " " * 28, 1), win.addstr.call_args_list)
|
||||
self.assertIn(mock.call(1, 1, "Message: ", 1), win.addstr.call_args_list)
|
||||
|
||||
def test_handle_ctrl_r_shows_context_when_message_id_is_unavailable(self) -> None:
|
||||
ui_state.current_window = 1
|
||||
ui_state.channel_list = ["Primary"]
|
||||
|
||||
@@ -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"))
|
||||
Reference in new issue
Block a user