mirror of
https://github.com/pdxlocations/contact.git
synced 2026-03-28 17:12:35 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3be31698df | ||
|
|
140d794213 | ||
|
|
8e6edf8e83 | ||
|
|
c97942d35d | ||
|
|
b9cecaea31 | ||
|
|
4bc1654eed | ||
|
|
ee5f2fa4d4 |
@@ -24,9 +24,9 @@ serial_enabled, "Enable serial console", ""
|
||||
button_gpio, "Button GPIO", "GPIO pin for user button."
|
||||
buzzer_gpio, "Buzzer GPIO", "GPIO pin for user buzzer."
|
||||
rebroadcast_mode, "Rebroadcast mode", "This setting defines the device's behavior for how messages are rebroadcast."
|
||||
node_info_broadcast_secs, "Nodeinfo broadcast interval", "This is the number of seconds between NodeInfo message broadcasts. Femtofox will still send nodeinfo in response to new nodes on the mesh."
|
||||
node_info_broadcast_secs, "Nodeinfo broadcast interval", "This is the number of seconds between NodeInfo message broadcasts. Will also send a nodeinfo in response to new nodes on the mesh."
|
||||
double_tap_as_button_press, "Double tap as button press", "This option will enable a double tap, when a supported accelerometer is attached to the device, to be treated as a button press."
|
||||
is_managed, "Enable managed mode", "Enabling Managed Mode blocks smartphone apps and web UI from changing configuration. [note]This setting is not required for remote node administration.[/note]Before enabling, verify that node can be controlled via Remote Admin to [warning]prevent being locked out.[/warning]"
|
||||
is_managed, "Enable managed mode", "Enabling Managed Mode blocks smartphone apps and web UI from changing configuration. [note]This setting is not required for remote node administration.[/note] Before enabling, verify that node can be controlled via Remote Admin to [warning]prevent being locked out.[/warning]"
|
||||
disable_triple_click, "Disable triple button press", ""
|
||||
tzdef, "Timezone", "Uses the TZ Database format to display the correct local time on the device display and in its logs."
|
||||
led_heartbeat_disabled, "Disable LED heartbeat", "On certain hardware models, this disables the blinking heartbeat LED."
|
||||
@@ -115,7 +115,7 @@ spread_factor, "Spread factor", "Indicates the number of chirps per symbol. Only
|
||||
coding_rate, "Coding rate", "The proportion of each LoRa transmission that contains actual data - the rest is used for error correction."
|
||||
frequency_offset, "Frequency offset", "This parameter is for advanced users with advanced test equipment."
|
||||
region, "Region", "Sets the region for your node. As long as this is not set, the node will display a message and not transmit any packets."
|
||||
hop_limit, "Hop limit", "The maximum number of intermediate nodes between Femtofox and a node it is sending to. Does not impact received messages.\n[warning]Excessive hop limit increases congestion![/warning]\nMust be between 0-7."
|
||||
hop_limit, "Hop limit", "The maximum number of intermediate nodes between our node and a node it is sending to. Does not impact received messages.\n[warning]Excessive hop limit increases congestion![/warning]\nMust be between 0-7."
|
||||
tx_enabled, "Enable TX", "Enables/disables the radio chip. Useful for hot-swapping antennas."
|
||||
tx_power, "TX power in dBm", "[warning]Setting a 33db radio above 8db will permanently damage it. ERP above 27db violates EU law. ERP above 36db violates US (unlicensed) law.[/warning] If 0, will use the max continuous power legal in region. Must be 0-30 (0=automatic)."
|
||||
channel_num, "Frequency slot", "Determines the exact frequency the radio transmits and receives. If unset or set to 0, determined automatically by the primary channel name."
|
||||
@@ -139,7 +139,7 @@ private_key, "Private key", "The private key of the device, used to create a sha
|
||||
is_managed, "Enable managed mode", "Enabling Managed Mode blocks smartphone apps and web UI from changing configuration. [note]This setting is not required for remote node administration.[/note]Before enabling, verify that node can be controlled via Remote Admin to [warning]prevent being locked out.[/warning]"
|
||||
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 Femtofox needs 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_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."
|
||||
|
||||
[module.mqtt]
|
||||
|
||||
2
main.py
2
main.py
@@ -4,6 +4,8 @@
|
||||
Contact - A Console UI for Meshtastic by http://github.com/pdxlocations
|
||||
Powered by Meshtastic.org
|
||||
V 1.2.2
|
||||
|
||||
Meshtastic® is a registered trademark of Meshtastic LLC. Meshtastic software components are released under various licenses, see GitHub for details. No warranty is provided - use at your own risk.
|
||||
'''
|
||||
|
||||
import contextlib
|
||||
|
||||
@@ -100,8 +100,14 @@ def display_menu(current_menu, menu_path, selected_index, show_save_option, help
|
||||
menu_win.getbegyx()[1] + menu_win.getmaxyx()[1] - 8
|
||||
)
|
||||
|
||||
max_index = num_items + (1 if show_save_option else 0) - 1
|
||||
visible_height = menu_win.getmaxyx()[0] - 5 - (2 if show_save_option else 0)
|
||||
|
||||
draw_arrows(menu_win, visible_height, max_index, start_index, show_save_option)
|
||||
|
||||
return menu_win, menu_pad
|
||||
|
||||
|
||||
def draw_help_window(menu_start_y, menu_start_x, menu_height, max_help_lines, current_menu, selected_index, transformed_path):
|
||||
global help_win
|
||||
|
||||
@@ -151,7 +157,6 @@ def update_help_window(help_win, help_text, transformed_path, selected_option, m
|
||||
return help_win
|
||||
|
||||
|
||||
|
||||
def get_wrapped_help_text(help_text, transformed_path, selected_option, width, max_lines):
|
||||
"""Fetches and formats help text for display, ensuring it fits within the allowed lines."""
|
||||
|
||||
@@ -252,10 +257,13 @@ def move_highlight(old_idx, new_idx, options, show_save_option, menu_win, menu_p
|
||||
visible_height = menu_win.getmaxyx()[0] - 5 - (2 if show_save_option else 0)
|
||||
|
||||
# Adjust start_index only when moving out of visible range
|
||||
if new_idx < start_index[-1]: # Moving above the visible area
|
||||
if new_idx == max_index and show_save_option:
|
||||
pass
|
||||
elif new_idx < start_index[-1]: # Moving above the visible area
|
||||
start_index[-1] = new_idx
|
||||
elif new_idx >= start_index[-1] + visible_height: # Moving below the visible area
|
||||
start_index[-1] = new_idx - visible_height
|
||||
start_index[-1] = new_idx - visible_height
|
||||
pass
|
||||
|
||||
# Ensure start_index is within bounds
|
||||
start_index[-1] = max(0, min(start_index[-1], max_index - visible_height + 1))
|
||||
@@ -278,7 +286,7 @@ def move_highlight(old_idx, new_idx, options, show_save_option, menu_win, menu_p
|
||||
menu_pad.refresh(start_index[-1], 0,
|
||||
menu_win.getbegyx()[0] + 3, menu_win.getbegyx()[1] + 4,
|
||||
menu_win.getbegyx()[0] + 3 + visible_height,
|
||||
menu_win.getbegyx()[1] + menu_win.getmaxyx()[1] - 8)
|
||||
menu_win.getbegyx()[1] + menu_win.getmaxyx()[1] - 4)
|
||||
|
||||
# Update help window
|
||||
transformed_path = transform_menu_path(menu_path)
|
||||
@@ -286,6 +294,25 @@ def move_highlight(old_idx, new_idx, options, show_save_option, menu_win, menu_p
|
||||
help_y = menu_win.getbegyx()[0] + menu_win.getmaxyx()[0]
|
||||
help_win = update_help_window(help_win, help_text, transformed_path, selected_option, max_help_lines, width, help_y, menu_win.getbegyx()[1])
|
||||
|
||||
draw_arrows(menu_win, visible_height, max_index, start_index, show_save_option)
|
||||
|
||||
|
||||
def draw_arrows(win, visible_height, max_index, start_index, show_save_option):
|
||||
|
||||
# vh = visible_height + (1 if show_save_option else 0)
|
||||
mi = max_index - (2 if show_save_option else 0)
|
||||
|
||||
if visible_height < mi:
|
||||
if start_index[-1] > 0:
|
||||
win.addstr(3, 2, "▲", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(3, 2, " ", get_color("settings_default"))
|
||||
|
||||
if mi - start_index[-1] >= visible_height + (0 if show_save_option else 1) :
|
||||
win.addstr(visible_height + 3, 2, "▼", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(visible_height + 3, 2, " ", get_color("settings_default"))
|
||||
|
||||
|
||||
def settings_menu(stdscr, interface):
|
||||
curses.update_lines_cols()
|
||||
@@ -359,10 +386,6 @@ def settings_menu(stdscr, interface):
|
||||
menu_win.refresh()
|
||||
help_win.refresh()
|
||||
|
||||
# Get the parent directory of the script
|
||||
app_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
config_folder = "node-configs"
|
||||
|
||||
if show_save_option and selected_index == len(options):
|
||||
save_changes(interface, menu_path, modified_settings)
|
||||
modified_settings.clear()
|
||||
@@ -374,7 +397,6 @@ def settings_menu(stdscr, interface):
|
||||
for step in menu_path[1:]:
|
||||
current_menu = current_menu.get(step, {})
|
||||
selected_index = 0
|
||||
|
||||
continue
|
||||
|
||||
selected_option = options[selected_index]
|
||||
@@ -393,7 +415,7 @@ def settings_menu(stdscr, interface):
|
||||
|
||||
try:
|
||||
config_text = config_export(interface)
|
||||
yaml_file_path = os.path.join(app_directory, config_folder, filename)
|
||||
yaml_file_path = os.path.join(config_folder, filename)
|
||||
|
||||
if os.path.exists(yaml_file_path):
|
||||
overwrite = get_list_input(f"{filename} already exists. Overwrite?", None, ["Yes", "No"])
|
||||
@@ -418,14 +440,13 @@ def settings_menu(stdscr, interface):
|
||||
continue
|
||||
|
||||
elif selected_option == "Load Config File":
|
||||
folder_path = os.path.join(app_directory, config_folder)
|
||||
|
||||
# Check if folder exists and is not empty
|
||||
if not os.path.exists(folder_path) or not any(os.listdir(folder_path)):
|
||||
if not os.path.exists(config_folder) or not any(os.listdir(config_folder)):
|
||||
dialog(stdscr, "", " No config files found. Export a config first.")
|
||||
continue # Return to menu
|
||||
|
||||
file_list = [f for f in os.listdir(folder_path) if os.path.isfile(os.path.join(folder_path, f))]
|
||||
file_list = [f for f in os.listdir(config_folder) if os.path.isfile(os.path.join(config_folder, f))]
|
||||
|
||||
# Ensure file_list is not empty before proceeding
|
||||
if not file_list:
|
||||
@@ -434,7 +455,7 @@ def settings_menu(stdscr, interface):
|
||||
|
||||
filename = get_list_input("Choose a config file", None, file_list)
|
||||
if filename:
|
||||
file_path = os.path.join(app_directory, config_folder, filename)
|
||||
file_path = os.path.join(config_folder, filename)
|
||||
overwrite = get_list_input(f"Are you sure you want to load {filename}?", None, ["Yes", "No"])
|
||||
if overwrite == "Yes":
|
||||
config_import(interface, file_path)
|
||||
|
||||
@@ -421,9 +421,9 @@ def draw_node_list():
|
||||
refresh_pad(2)
|
||||
|
||||
# Restore cursor to input field
|
||||
entry_win.move(1, len("Input: ") + len(input_text)+1)
|
||||
entry_win.refresh()
|
||||
entry_win.keypad(True)
|
||||
curses.curs_set(1)
|
||||
entry_win.refresh()
|
||||
|
||||
def select_channel(idx):
|
||||
old_selected_channel = globals.selected_channel
|
||||
@@ -522,9 +522,9 @@ def draw_packetlog_win():
|
||||
packetlog_win.refresh()
|
||||
|
||||
# Restore cursor to input field
|
||||
entry_win.move(1, len("Input: ") + len(input_text)+1)
|
||||
entry_win.refresh()
|
||||
entry_win.keypad(True)
|
||||
curses.curs_set(1)
|
||||
entry_win.refresh()
|
||||
|
||||
def search(win):
|
||||
start_idx = globals.selected_node
|
||||
|
||||
@@ -190,12 +190,16 @@ def maybe_store_nodeinfo_in_db(packet):
|
||||
except Exception as e:
|
||||
logging.error(f"Unexpected error in maybe_store_nodeinfo_in_db: {e}")
|
||||
|
||||
|
||||
def update_node_info_in_db(user_id, long_name=None, short_name=None, hw_model=None, is_licensed=None, role=None, public_key=None, chat_archived=None):
|
||||
def update_node_info_in_db(user_id, long_name=None, short_name=None, hw_model="UNSET", is_licensed=0, role="CLIENT", public_key="", chat_archived=0):
|
||||
"""Update or insert node information into the database, preserving unchanged fields."""
|
||||
try:
|
||||
ensure_node_table_exists() # Ensure the table exists before any operation
|
||||
|
||||
if long_name == None:
|
||||
long_name = "Meshtastic " + str(decimal_to_hex(user_id)[-4:])
|
||||
if short_name == None:
|
||||
short_name = str(decimal_to_hex(user_id)[-4:])
|
||||
|
||||
with sqlite3.connect(config.db_file_path) as db_connection:
|
||||
db_cursor = db_connection.cursor()
|
||||
table_name = f'"{globals.myNodeNum}_nodedb"' # Quote in case of numeric names
|
||||
|
||||
@@ -372,6 +372,11 @@ def get_list_input(prompt, current_option, list_options):
|
||||
list_pad.refresh(0, 0,
|
||||
list_win.getbegyx()[0] + 3, list_win.getbegyx()[1] + 4,
|
||||
list_win.getbegyx()[0] + list_win.getmaxyx()[0] - 2, list_win.getbegyx()[1] + list_win.getmaxyx()[1] - 4)
|
||||
|
||||
max_index = len(list_options) - 1
|
||||
visible_height = list_win.getmaxyx()[0] - 5
|
||||
|
||||
draw_arrows(list_win, visible_height, max_index, 0)
|
||||
|
||||
while True:
|
||||
key = list_win.getch()
|
||||
@@ -385,8 +390,12 @@ def get_list_input(prompt, current_option, list_options):
|
||||
selected_index = min(len(list_options) - 1, selected_index + 1)
|
||||
move_highlight(old_selected_index, selected_index, list_options, list_win, list_pad)
|
||||
elif key == ord('\n'): # Enter key
|
||||
list_win.clear()
|
||||
list_win.refresh()
|
||||
return list_options[selected_index]
|
||||
elif key == 27 or key == curses.KEY_LEFT: # ESC or Left Arrow
|
||||
list_win.clear()
|
||||
list_win.refresh()
|
||||
return current_option
|
||||
|
||||
|
||||
@@ -424,5 +433,22 @@ def move_highlight(old_idx, new_idx, options, list_win, list_pad):
|
||||
list_win.getbegyx()[0] + 3, list_win.getbegyx()[1] + 4,
|
||||
list_win.getbegyx()[0] + 3 + visible_height,
|
||||
list_win.getbegyx()[1] + list_win.getmaxyx()[1] - 4)
|
||||
|
||||
draw_arrows(list_win, visible_height, max_index, scroll_offset)
|
||||
|
||||
return scroll_offset # Return updated scroll_offset to be stored externally
|
||||
return scroll_offset # Return updated scroll_offset to be stored externally
|
||||
|
||||
|
||||
def draw_arrows(win, visible_height, max_index, start_index):
|
||||
|
||||
if visible_height < max_index:
|
||||
if start_index > 0:
|
||||
win.addstr(3, 2, "▲", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(3, 2, " ", get_color("settings_default"))
|
||||
|
||||
if max_index - start_index > visible_height:
|
||||
win.addstr(visible_height + 3, 2, "▼", get_color("settings_default"))
|
||||
else:
|
||||
win.addstr(visible_height + 3, 2, " ", get_color("settings_default"))
|
||||
|
||||
Reference in New Issue
Block a user