mirror of
https://github.com/pdxlocations/contact.git
synced 2026-03-28 17:12:35 +01:00
Compare commits
11 Commits
UIState
...
client-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f270b5ba5 | ||
|
|
4abe9611e3 | ||
|
|
4d20df17fe | ||
|
|
3bb57b9420 | ||
|
|
e305bb4464 | ||
|
|
636b27cf9b | ||
|
|
8e500cb305 | ||
|
|
0878937194 | ||
|
|
ac2016322b | ||
|
|
031d74a290 | ||
|
|
14913ce5ae |
@@ -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
|
||||
```
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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"}
|
||||
|
||||
Reference in New Issue
Block a user