Compare commits

...

11 Commits

Author SHA1 Message Date
pdxlocations
1f270b5ba5 devpath 2025-04-08 20:50:30 -07:00
pdxlocations
4abe9611e3 bump version 2025-04-06 22:03:05 -07:00
pdxlocations
4d20df17fe Update README.md 2025-04-06 21:59:01 -07:00
pdxlocations
3bb57b9420 Merge pull request #163 from pdxlocations:localhost-fallback
Fallback to localhost not meshtastic.local
2025-04-06 21:44:32 -07:00
pdxlocations
e305bb4464 use localhost not meshtastic.local 2025-04-06 21:44:04 -07:00
pdxlocations
636b27cf9b fix typo 2025-04-06 21:28:07 -07:00
pdxlocations
8e500cb305 bump version 2025-04-06 20:19:44 -07:00
pdxlocations
0878937194 correct instructions for launching control 2025-04-06 20:17:03 -07:00
pdxlocations
ac2016322b update en.ini 2025-04-05 22:35:35 -07:00
pdxlocations
031d74a290 Fix options "not set" displaying values 2025-04-05 22:20:19 -07:00
pdxlocations
14913ce5ae fix new_idx 2025-04-05 21:22:19 -07:00
6 changed files with 61 additions and 21 deletions

View File

@@ -9,9 +9,9 @@ This Python curses client for Meshtastic is a terminal-based client designed to
<img width="846" alt="Contact - Main UI Screenshot" src="https://github.com/user-attachments/assets/d2996bfb-2c6d-46a8-b820-92a9143375f4">
<br><br>
The settings dialogue can be accessed within the client or may be run standalone to configure your node by launching `settings.py`
The settings dialogue can be accessed within the client or may be run standalone to configure your node by launching `contact --settings` or `contact -c`
<img width="441" alt="Contact - Settings Dialogue" src="https://github.com/user-attachments/assets/dd47f52a-d4d8-4e40-8001-9ea53d87f816" />
<img width="573" alt="Contact - Settings Dialogue" src="https://github.com/user-attachments/assets/dbe1287b-5558-407c-84b8-2a1bc913dec8" />
## Message Persistence
@@ -62,4 +62,4 @@ contact --ble BlAddressOfDevice
To quickly connect to localhost, use:
```sh
contact -t
```
```

View File

@@ -78,6 +78,13 @@ dns, "IPv4 DNS server", ""
rsyslog_server, "RSyslog server", ""
enabled_protocols, "Enabled protocols", ""
[config.network.ipv4_config]
title, "IPv4 Config", ""
ip, "IP", ""
gateway, "Gateway", ""
subnet, "Subnet", ""
dns, "DNS", ""
[config.display]
title, "Display"
screen_on_secs, "Screen on duration", "How long the screen remains on in seconds after the user button is pressed or messages are received."
@@ -105,6 +112,35 @@ theme, "Theme", ""
alert_enabled, "Alert enabled", ""
banner_enabled, "Banner enabled", ""
ring_tone_id, "Ring tone ID", ""
language, "Language", ""
node_filter, "Node Filter", ""
node_highlight, "Node Highlight", ""
calibration_data, "Calibration Data", ""
map_data, "Map Data", ""
[config.device_ui.node_filter]
title, "Node Filter"
unknown_switch, "Unknown Switch", ""
offline_switch, "Offline Switch", ""
public_key_switch, "Public Key Switch", ""
hops_away, "Hops Away", ""
position_switch, "Position Switch", ""
node_name, "Node Name", ""
channel, "Channel", ""
[config.device_ui.node_highlight]
title, "Node Highlight"
chat_switch, "Chat Switch", ""
position_switch, "Position Switch", ""
telemetry_switch, "Telemetry Switch", ""
iaq_switch, "IAQ Switch", ""
node_name, "Node Name", ""
[config.device_ui.map_data]
title, "Map Data"
home, "Home", ""
style, "Style", ""
follow_gps, "Follow GPS", ""
[config.lora]
title, "LoRa"

View File

@@ -565,8 +565,11 @@ def settings_menu(stdscr, interface):
state.start_index.pop()
elif field.type == 8: # Handle boolean type
new_value = get_list_input(human_readable_name, str(current_value), ["True", "False"])
new_value = new_value == "True" or new_value is True
new_value = get_list_input(human_readable_name, str(current_value), ["True", "False"])
if new_value == "Not Set":
pass # Leave it as-is
else:
new_value = new_value == "True" or new_value is True
state.start_index.pop()
elif field.label == field.LABEL_REPEATED: # Handle repeated field - Not currently used

View File

@@ -167,20 +167,20 @@ def display_menu(state):
return menu_win, menu_pad, options
def move_highlight(old_idx, new_idx, options, menu_win, menu_pad, state):
if old_idx == new_idx: # No-op
def move_highlight(old_idx, options, menu_win, menu_pad, state):
if old_idx == state.selected_index: # No-op
return
max_index = len(options) + (1 if state.show_save_option else 0) - 1
visible_height = menu_win.getmaxyx()[0] - 5 - (2 if state.show_save_option else 0)
# Adjust state.start_index only when moving out of visible range
if new_idx == max_index and state.show_save_option:
if state.selected_index == max_index and state.show_save_option:
pass
elif new_idx < state.start_index[-1]: # Moving above the visible area
state.start_index[-1] = new_idx
elif new_idx >= state.start_index[-1] + visible_height: # Moving below the visible area
state.start_index[-1] = new_idx - visible_height
elif state.selected_index < state.start_index[-1]: # Moving above the visible area
state.start_index[-1] = state.selected_index
elif state.selected_index >= state.start_index[-1] + visible_height: # Moving below the visible area
state.start_index[-1] = state.selected_index - visible_height
pass
# Ensure state.start_index is within bounds
@@ -193,10 +193,10 @@ def move_highlight(old_idx, new_idx, options, menu_win, menu_pad, state):
menu_pad.chgat(old_idx, 0, menu_pad.getmaxyx()[1], get_color("settings_sensitive") if options[old_idx] in sensitive_settings else get_color("settings_default"))
# Highlight new selection
if state.show_save_option and new_idx == max_index:
if state.show_save_option and state.selected_index == max_index:
menu_win.chgat(menu_win.getmaxyx()[0] - 2, (width - len(save_option)) // 2, len(save_option), get_color("settings_save", reverse=True))
else:
menu_pad.chgat(new_idx, 0, menu_pad.getmaxyx()[1], get_color("settings_sensitive", reverse=True) if options[new_idx] in sensitive_settings else get_color("settings_default", reverse=True))
menu_pad.chgat(state.selected_index, 0, menu_pad.getmaxyx()[1], get_color("settings_sensitive", reverse=True) if options[state.selected_index] in sensitive_settings else get_color("settings_default", reverse=True))
menu_win.refresh()
@@ -264,18 +264,18 @@ def json_editor(stdscr, state):
old_selected_index = state.selected_index
state.selected_index = max_index if state.selected_index == 0 else state.selected_index - 1
move_highlight(old_selected_index, state.selected_index, options, state.show_save_option, menu_win, menu_pad,state)
move_highlight(old_selected_index, options, menu_win, menu_pad, state)
elif key == curses.KEY_DOWN:
old_selected_index = state.selected_index
state.selected_index = 0 if state.selected_index == max_index else state.selected_index + 1
move_highlight(old_selected_index, state.selected_index, options, menu_win, menu_pad, state)
move_highlight(old_selected_index, options, menu_win, menu_pad, state)
elif key == ord("\t") and state.show_save_option:
old_selected_index = state.selected_index
state.selected_index = max_index
move_highlight(old_selected_index, state.selected_index, options, menu_win, menu_pad, state)
move_highlight(old_selected_index, options, menu_win, menu_pad, state)
elif key in (curses.KEY_RIGHT, 10, 13): # 10 = \n, 13 = carriage return

View File

@@ -10,14 +10,15 @@ def initialize_interface(args):
return meshtastic.tcp_interface.TCPInterface(args.host)
else:
try:
return meshtastic.serial_interface.SerialInterface(args.port)
client = meshtastic.serial_interface.SerialInterface(args.port)
except PermissionError as ex:
logging.error(f"You probably need to add yourself to the `dialout` group to use a serial connection. {ex}")
except Exception as ex:
logging.error(f"Unexpected error initializing interface: {ex}")
if globals.interface.devPath is None:
return meshtastic.tcp_interface.TCPInterface("meshtastic.local")
if client.devPath is None:
client = meshtastic.tcp_interface.TCPInterface("localhost")
return client
except Exception as ex:
logging.critical(f"Fatal error initializing interface: {ex}")

View File

@@ -1,6 +1,6 @@
[project]
name = "contact"
version = "1.3.2"
version = "1.3.4"
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"}