Compare commits

...

14 Commits

Author SHA1 Message Date
pdxlocations
f4115e48ad Merge pull request #148 from pdxlocations:poetry
Package for Poetry and Pypi
2025-04-01 15:53:20 -07:00
pdxlocations
857d8d0c04 update readme 2025-04-01 15:52:57 -07:00
pdxlocations
ec0554df14 working changes 2025-04-01 14:58:35 -07:00
pdxlocations
372204a684 mcontact -> contact 2025-04-01 14:57:51 -07:00
pdxlocations
2b0f6515af update workflow 2025-03-30 19:15:59 -07:00
pdxlocations
4cd7c4e24d change email 2025-03-30 16:28:26 -07:00
pdxlocations
92a30ad8b0 flix user config location 2025-03-27 21:14:51 -07:00
pdxlocations
2960553fef rm extra file 2025-03-27 21:08:26 -07:00
pdxlocations
37c5a7dbc3 fix some imports 2025-03-27 21:07:15 -07:00
pdxlocations
25240f211b add release.yaml 2025-03-27 20:55:50 -07:00
pdxlocations
c236a386a5 mcontact 2025-03-26 22:30:35 -07:00
pdxlocations
66a9954149 mtcontact to mcontact 2025-03-26 22:27:03 -07:00
pdxlocations
3df012dd69 add launch.json 2025-03-26 21:59:33 -07:00
vidplace7
6d204581ce Package with poetry 2025-03-25 09:00:37 -04:00
27 changed files with 256 additions and 69 deletions

143
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,143 @@
name: release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
env:
PACKAGE_NAME: "contact"
OWNER: "pdxlocations"
jobs:
details:
runs-on: ubuntu-latest
outputs:
new_version: ${{ steps.release.outputs.new_version }}
suffix: ${{ steps.release.outputs.suffix }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: actions/checkout@v2
- name: Extract tag and Details
id: release
run: |
if [ "${{ github.ref_type }}" = "tag" ]; then
TAG_NAME=${GITHUB_REF#refs/tags/}
NEW_VERSION=$(echo $TAG_NAME | awk -F'-' '{print $1}')
SUFFIX=$(echo $TAG_NAME | grep -oP '[a-z]+[0-9]+' || echo "")
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "suffix=$SUFFIX" >> "$GITHUB_OUTPUT"
echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT"
echo "Version is $NEW_VERSION"
echo "Suffix is $SUFFIX"
echo "Tag name is $TAG_NAME"
else
echo "No tag found"
exit 1
fi
check_pypi:
needs: details
runs-on: ubuntu-latest
steps:
- name: Fetch information from PyPI
run: |
response=$(curl -s https://pypi.org/pypi/${{ env.PACKAGE_NAME }}/json || echo "{}")
latest_previous_version=$(echo $response | jq --raw-output "select(.releases != null) | .releases | keys_unsorted | last")
if [ -z "$latest_previous_version" ]; then
echo "Package not found on PyPI."
latest_previous_version="0.0.0"
fi
echo "Latest version on PyPI: $latest_previous_version"
echo "latest_previous_version=$latest_previous_version" >> $GITHUB_ENV
- name: Compare versions and exit if not newer
run: |
NEW_VERSION=${{ needs.details.outputs.new_version }}
LATEST_VERSION=$latest_previous_version
if [ "$(printf '%s\n' "$LATEST_VERSION" "$NEW_VERSION" | sort -rV | head -n 1)" != "$NEW_VERSION" ] || [ "$NEW_VERSION" == "$LATEST_VERSION" ]; then
echo "The new version $NEW_VERSION is not greater than the latest version $LATEST_VERSION on PyPI."
exit 1
else
echo "The new version $NEW_VERSION is greater than the latest version $LATEST_VERSION on PyPI."
fi
setup_and_build:
needs: [details, check_pypi]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Set project version with Poetry
run: |
poetry version ${{ needs.details.outputs.new_version }}
- name: Install dependencies
run: poetry install --sync --no-interaction
- name: Build source and wheel distribution
run: |
poetry build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
pypi_publish:
name: Upload release to PyPI
needs: [setup_and_build, details]
runs-on: ubuntu-latest
environment:
name: release
permissions:
id-token: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
github_release:
name: Create GitHub Release
needs: [setup_and_build, details]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Create GitHub Release
id: create_release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create ${{ needs.details.outputs.tag_name }} dist/* --title ${{ needs.details.outputs.tag_name }} --generate-notes

3
.gitignore vendored
View File

@@ -7,4 +7,5 @@ client.db
client.log
settings.log
config.json
default_config.log
default_config.log
dist/

13
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"version": "0.1.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"cwd": "${workspaceFolder}",
"module": "contact.main",
"args": []
}
]
}

View File

@@ -54,11 +54,11 @@ If no connection arguments are specified, the client will attempt a serial conne
### Example Usage
```sh
python main.py --port /dev/ttyUSB0
python main.py --host 192.168.1.1
python main.py --ble BlAddressOfDevice
contact --port /dev/ttyUSB0
contact --host 192.168.1.1
contact --ble BlAddressOfDevice
```
To quickly connect to localhost, use:
```sh
python main.py -t
contact -t
```

View File

@@ -18,18 +18,18 @@ import logging
import traceback
import threading
from utilities.db_handler import init_nodedb, load_messages_from_db
from message_handlers.rx_handler import on_receive
from settings import set_region
from ui.curses_ui import main_ui
from ui.colors import setup_colors
from ui.splash import draw_splash
import ui.default_config as config
from utilities.arg_parser import setup_parser
from utilities.interfaces import initialize_interface
from utilities.input_handlers import get_list_input
from utilities.utils import get_channels, get_node_list, get_nodeNum
import globals
from contact.utilities.db_handler import init_nodedb, load_messages_from_db
from contact.message_handlers.rx_handler import on_receive
from contact.settings import set_region
from contact.ui.curses_ui import main_ui
from contact.ui.colors import setup_colors
from contact.ui.splash import draw_splash
import contact.ui.default_config as config
from contact.utilities.arg_parser import setup_parser
from contact.utilities.interfaces import initialize_interface
from contact.utilities.input_handlers import get_list_input
from contact.utilities.utils import get_channels, get_node_list, get_nodeNum
import contact.globals as globals
# Set ncurses compatibility settings
os.environ["NCURSES_NO_UTF8_ACS"] = "1"
@@ -87,7 +87,7 @@ def main(stdscr):
logging.error("Console output before crash:\n%s", console_output)
raise # Re-raise only unexpected errors
if __name__ == "__main__":
def start():
log_file = config.log_file_path
log_f = open(log_file, "a", buffering=1) # Enable line-buffering for immediate log writes
@@ -103,4 +103,7 @@ if __name__ == "__main__":
except Exception as e:
logging.error("Fatal error in curses wrapper: %s", e)
logging.error("Traceback: %s", traceback.format_exc())
sys.exit(1) # Exit with an error code
sys.exit(1) # Exit with an error code
if __name__ == "__main__":
start()

View File

@@ -1,11 +1,11 @@
import logging
import time
from utilities.utils import refresh_node_list
from contact.utilities.utils import refresh_node_list
from datetime import datetime
from ui.curses_ui import draw_packetlog_win, draw_node_list, draw_messages_window, draw_channel_list, add_notification
from utilities.db_handler import save_message_to_db, maybe_store_nodeinfo_in_db, get_name_from_database, update_node_info_in_db
import ui.default_config as config
import globals
from contact.ui.curses_ui import draw_packetlog_win, draw_node_list, draw_messages_window, draw_channel_list, add_notification
from contact.utilities.db_handler import save_message_to_db, maybe_store_nodeinfo_in_db, get_name_from_database, update_node_info_in_db
import contact.ui.default_config as config
import contact.globals as globals
from datetime import datetime

View File

@@ -3,16 +3,16 @@ import google.protobuf.json_format
from meshtastic import BROADCAST_NUM
from meshtastic.protobuf import mesh_pb2, portnums_pb2
from utilities.db_handler import save_message_to_db, update_ack_nak, get_name_from_database, is_chat_archived, update_node_info_in_db
import ui.default_config as config
import globals
from contact.utilities.db_handler import save_message_to_db, update_ack_nak, get_name_from_database, is_chat_archived, update_node_info_in_db
import contact.ui.default_config as config
import contact.globals as globals
ack_naks = {}
# Note "onAckNak" has special meaning to the API, thus the nonstandard naming convention
# See https://github.com/meshtastic/python/blob/master/meshtastic/mesh_interface.py#L462
def onAckNak(packet):
from ui.curses_ui import draw_messages_window
from contact.ui.curses_ui import draw_messages_window
request = packet['decoded']['requestId']
if(request not in ack_naks):
return
@@ -43,7 +43,7 @@ def onAckNak(packet):
def on_response_traceroute(packet):
"""on response for trace route"""
from ui.curses_ui import draw_channel_list, draw_messages_window, add_notification
from contact.ui.curses_ui import draw_channel_list, draw_messages_window, add_notification
refresh_channels = False
refresh_messages = False

View File

@@ -5,13 +5,13 @@ import logging
import sys
import traceback
import ui.default_config as config
from utilities.input_handlers import get_list_input
from ui.colors import setup_colors
from ui.splash import draw_splash
from ui.control_ui import set_region, settings_menu
from utilities.arg_parser import setup_parser
from utilities.interfaces import initialize_interface
import contact.ui.default_config as config
from contact.utilities.input_handlers import get_list_input
from contact.ui.colors import setup_colors
from contact.ui.splash import draw_splash
from contact.ui.control_ui import set_region, settings_menu
from contact.utilities.arg_parser import setup_parser
from contact.utilities.interfaces import initialize_interface
def main(stdscr):

View File

@@ -1,5 +1,5 @@
import curses
import ui.default_config as config
import contact.ui.default_config as config
COLOR_MAP = {
"black": curses.COLOR_BLACK,

View File

@@ -5,14 +5,16 @@ import os
import re
import sys
from utilities.save_to_radio import save_changes
from utilities.config_io import config_export, config_import
from utilities.input_handlers import get_repeated_input, get_text_input, get_fixed32_input, get_list_input, get_admin_key_input
from ui.menus import generate_menu_from_protobuf
from ui.colors import get_color
from ui.dialog import dialog
from utilities.control_utils import parse_ini_file, transform_menu_path
from ui.user_config import json_editor
from contact.utilities.save_to_radio import save_changes
from contact.utilities.config_io import config_export, config_import
from contact.utilities.input_handlers import get_repeated_input, get_text_input, get_fixed32_input, get_list_input, get_admin_key_input
from contact.ui.menus import generate_menu_from_protobuf
from contact.ui.colors import get_color
from contact.ui.dialog import dialog
from contact.utilities.control_utils import parse_ini_file, transform_menu_path
from contact.ui.user_config import json_editor
import contact.localisations
# Constants
width = 80
@@ -27,8 +29,9 @@ parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
# Paths
locals_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # Current script directory
translation_file = os.path.join(locals_dir, "localisations", "en.ini")
config_folder = os.path.join(parent_dir, "node-configs")
translation_file = os.path.join(parent_dir, "localisations", "en.ini")
config_folder = os.path.join(locals_dir, "node-configs")
# Load translations
field_mapping, help_text = parse_ini_file(translation_file)

View File

@@ -2,14 +2,14 @@ import curses
import textwrap
import logging
import traceback
from utilities.utils import get_channels, get_readable_duration, get_time_ago, refresh_node_list
from settings import settings_menu
from message_handlers.tx_handler import send_message, send_traceroute
from ui.colors import setup_colors, get_color
from utilities.db_handler import get_name_from_database, update_node_info_in_db, is_chat_archived
import ui.default_config as config
import ui.dialog
import globals
from contact.utilities.utils import get_channels, get_readable_duration, get_time_ago, refresh_node_list
from contact.settings import settings_menu
from contact.message_handlers.tx_handler import send_message, send_traceroute
from contact.ui.colors import setup_colors, get_color
from contact.utilities.db_handler import get_name_from_database, update_node_info_in_db, is_chat_archived
import contact.ui.default_config as config
import contact.ui.dialog
import contact.globals as globals
def handle_resize(stdscr, firstrun):
global messages_pad, messages_win, nodes_pad, nodes_win, channel_pad, channel_win, function_win, packetlog_win, entry_win
@@ -212,7 +212,7 @@ def main_ui(stdscr):
elif char == chr(20):
send_traceroute()
curses.curs_set(0) # Hide cursor
ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window.\nNote: Traceroute is limited to once every 30 seconds.")
contact.ui.dialog.dialog(stdscr, "Traceroute Sent", "Results will appear in messages window.\nNote: Traceroute is limited to once every 30 seconds.")
curses.curs_set(1) # Show cursor again
handle_resize(stdscr, False)

View File

@@ -1,5 +1,5 @@
import curses
from ui.colors import get_color
from contact.ui.colors import get_color
def dialog(stdscr, title, message):
height, width = stdscr.getmaxyx()

View File

@@ -1,5 +1,5 @@
import curses
from ui.colors import get_color
from contact.ui.colors import get_color
def draw_splash(stdscr):
curses.curs_set(0)

View File

@@ -1,9 +1,9 @@
import os
import json
import curses
from ui.colors import get_color, setup_colors, COLOR_MAP
from ui.default_config import format_json_single_line_arrays, loaded_config
from utilities.input_handlers import get_list_input
from contact.ui.colors import get_color, setup_colors, COLOR_MAP
from contact.ui.default_config import format_json_single_line_arrays, loaded_config
from contact.utilities.input_handlers import get_list_input
width = 60
save_option_text = "Save Changes"
@@ -196,7 +196,10 @@ def json_editor(stdscr):
menu_path = ["App Settings"]
selected_index = 0 # Track the selected option
file_path = "config.json"
script_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.abspath(os.path.join(script_dir, os.pardir))
file_path = os.path.join(parent_dir, "config.json")
# file_path = "config.json"
show_save_option = True # Always show the Save button
# Ensure the file exists

View File

@@ -3,9 +3,9 @@ import time
import logging
from datetime import datetime
from utilities.utils import decimal_to_hex
import ui.default_config as config
import globals
from contact.utilities.utils import decimal_to_hex
import contact.ui.default_config as config
import contact.globals as globals
def get_table_name(channel):
# Construct the table name

View File

@@ -3,7 +3,7 @@ import binascii
import curses
import ipaddress
import re
from ui.colors import get_color
from contact.ui.colors import get_color
def wrap_text(text, wrap_width):
"""Wraps text while preserving spaces and breaking long words."""

View File

@@ -1,6 +1,6 @@
import logging
import meshtastic.serial_interface, meshtastic.tcp_interface, meshtastic.ble_interface
import globals
import contact.globals as globals
def initialize_interface(args):
try:

View File

@@ -1,7 +1,7 @@
import globals
import contact.globals as globals
import datetime
from meshtastic.protobuf import config_pb2
import ui.default_config as config
import contact.ui.default_config as config
def get_channels():
"""Retrieve channels from the node and update globals.channel_list and globals.all_messages."""

21
pyproject.toml Normal file
View File

@@ -0,0 +1,21 @@
[project]
name = "contact"
version = "1.3.0"
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"}
]
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.9,<3.14"
dependencies = [
"meshtastic (>=2.6.0,<3.0.0)"
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
contact = "contact.main:start"