What makes us different from other similar websites? › Forums › Tech › Meshtastic App || Custom || Linux
Tagged: App. Mesh Gui App, GUI, Linux, Mesh, Meshtastic, Meshtastic Gui App
- This topic has 0 replies, 1 voice, and was last updated 2 weeks, 3 days ago by
thumbtak.
Viewing 1 post (of 1 total)
-
AuthorPosts
-
February 25, 2026 at 1:11 pm #8424
thumbtakModeratorSave as something like this
mesh.py, and run as something like thispython3 mesh.py.import sys import subprocess import importlib import time # --- AUTO-INSTALLER ENGINE --- def install_dependencies(): required = {"PySide6": "pyside6", "meshtastic": "meshtastic"} for name, package in required.items(): try: importlib.import_module(name if name != "PySide6" else "PySide6") except ImportError: print(f"Missing {name}... Installing for Xubuntu...") subprocess.check_call([sys.executable, "-m", "pip", "install", package]) install_dependencies() from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTextEdit, QLineEdit, QLabel, QListWidget, QFrame, QGraphicsDropShadowEffect) from PySide6.QtCore import Qt, QTimer, QSize from PySide6.QtGui import QColor, QFont, QIcon from meshtastic.serial_interface import SerialInterface from pubsub import pub # --- PROFESSIONAL NEON THEME --- PINK = "#ff66cc" # MQTT / Brand CYAN = "#00ffff" # Local RF GRAY = "#333333" # Offline BG_DARK = "#0a0a0b" CARD_BG = "#16161a" class ProfessionalMeshGUI(QWidget): def __init__(self): super().__init__() self.setWindowTitle("MESHTASTIC COMMAND v2.0") self.resize(1200, 800) self.active_channel = 0 self.interface = None # Main UI Styling self.setStyleSheet(f""" QWidget {{ background-color: {BG_DARK}; color: #f0f0f0; font-family: 'Inter', 'Ubuntu', sans-serif; }} /* Sidebar Styling */ QListWidget {{ background-color: {CARD_BG}; border: none; border-radius: 8px; padding: 10px; font-size: 13px; outline: none; }} QListWidget::item {{ padding: 10px; border-radius: 4px; margin-bottom: 5px; }} QListWidget::item:selected {{ background-color: {PINK}; color: black; font-weight: bold; }} /* Chat & Input */ QTextEdit {{ background-color: {CARD_BG}; border: 1px solid #222; border-radius: 12px; padding: 15px; line-height: 1.5; }} QLineEdit {{ background-color: #1c1c21; border: 1px solid #333; border-radius: 8px; padding: 12px; color: white; font-size: 14px; }} QLineEdit:focus {{ border: 1px solid {PINK}; }} /* The Professional Send Button */ QPushButton#SendButton {{ background-color: {PINK}; color: {BG_DARK}; border-radius: 8px; font-weight: 800; font-size: 12px; padding: 10px 25px; text-transform: uppercase; }} QPushButton#SendButton:hover {{ background-color: #ff85d5; }} QPushButton#SendButton:pressed {{ background-color: #d455a8; }} """) self.init_ui() self.connect_radio() def init_ui(self): main_layout = QHBoxLayout(self) main_layout.setContentsMargins(20, 20, 20, 20) main_layout.setSpacing(20) # --- LEFT COLUMN (Navigation) --- nav_col = QVBoxLayout() nav_col.addWidget(self.create_label("CHANNELS")) self.chan_list = QListWidget() self.chan_list.itemClicked.connect(self.select_channel) nav_col.addWidget(self.chan_list) # Stats Card self.stats_card = QFrame() self.stats_card.setStyleSheet(f"background: {CARD_BG}; border-radius: 10px; padding: 10px;") stats_lay = QVBoxLayout(self.stats_card) self.status_dot = QLabel("● SYSTEM READY") self.status_dot.setStyleSheet(f"color: {CYAN}; font-size: 10px; font-weight: bold;") stats_lay.addWidget(self.status_dot) nav_col.addWidget(self.stats_card) main_layout.addLayout(nav_col, 1) # --- CENTER COLUMN (Messaging) --- chat_col = QVBoxLayout() self.chat_display = QTextEdit() self.chat_display.setReadOnly(True) chat_col.addWidget(self.chat_display) input_container = QHBoxLayout() self.msg_input = QLineEdit() self.msg_input.setPlaceholderText("Write a secure message...") self.msg_input.returnPressed.connect(self.send_msg) self.send_btn = QPushButton("SEND") self.send_btn.setObjectName("SendButton") self.send_btn.clicked.connect(self.send_msg) input_container.addWidget(self.msg_input) input_container.addWidget(self.send_btn) chat_col.addLayout(input_container) main_layout.addLayout(chat_col, 3) # --- RIGHT COLUMN (Nodes & Legend) --- node_col = QVBoxLayout() node_col.addWidget(self.create_label("NETWORK NODES")) self.node_list = QListWidget() node_col.addWidget(self.node_list) # Legend legend = QFrame() legend.setStyleSheet(f"background: #000; border: 1px solid #222; border-radius: 8px; padding: 12px;") leg_lay = QVBoxLayout(legend) leg_lay.addWidget(self.create_label("CONNECTIVITY")) leg_lay.addWidget(self.create_legend_item(CYAN, "LOCAL RF")) leg_lay.addWidget(self.create_legend_item(PINK, "MQTT BRIDGE")) leg_lay.addWidget(self.create_legend_item(GRAY, "OFFLINE")) node_col.addWidget(legend) main_layout.addLayout(node_col, 1) def create_label(self, text): lbl = QLabel(text) lbl.setStyleSheet(f"color: #666; font-weight: bold; font-size: 10px; letter-spacing: 2px; margin-bottom: 5px;") return lbl def create_legend_item(self, color, text): lbl = QLabel(f"<span style="color: {color};">●</span> {text}") lbl.setStyleSheet("font-size: 11px; color: #aaa;") return lbl def connect_radio(self): try: self.interface = SerialInterface() self.refresh_ui() pub.subscribe(self.on_rx, "meshtastic.receive.text") self.timer = QTimer() self.timer.timeout.connect(self.update_nodes) self.timer.start(300000) except Exception as e: self.status_dot.setText(f"● OFFLINE: {str(e)[:20]}...") self.status_dot.setStyleSheet(f"color: {OFFLINE_GRAY};") def refresh_ui(self): if not self.interface: return self.chan_list.clear() for i, ch in enumerate(self.interface.localNode.channels): if ch.role: self.chan_list.addItem(f"CH {i}: {ch.settings.name or 'Primary'}") self.update_nodes() def update_nodes(self): if not self.interface: return self.node_list.clear() now = time.time() for n in self.interface.nodes.values(): name = n.get('user', {}).get('longName', f"Node {n.get('num')}") last_h = n.get('lastHeard', 0) is_mqtt = n.get('viaMqtt', False) or (n.get('snr', 0) == 0 and last_h > 0) if (now - last_h) > 1800: color = GRAY elif is_mqtt: color = PINK else: color = CYAN self.node_list.addItem(f"● {name}") self.node_list.item(self.node_list.count()-1).setForeground(QColor(color)) def select_channel(self, item): self.active_channel = int(item.text().split(":")[0].replace("CH ", "")) self.status_dot.setText(f"● CH {self.active_channel} ACTIVE") def send_msg(self): if self.interface and self.msg_input.text(): self.interface.sendText(self.msg_input.text(), channelIndex=self.active_channel) self.chat_display.append(f" <div style="margin-bottom: 10px;"><b style="color: {pink};">ME:</b> {self.msg_input.text()}</div> ") self.msg_input.clear() def on_rx(self, packet, interface): txt = packet.get('decoded', {}).get('text', '') sender = packet.get('fromId', 'Unknown') self.chat_display.append(f" <div style="margin-bottom: 10px;"><b style="color: {cyan};">{sender}:</b> {txt}</div> ") self.update_nodes() if __name__ == "__main__": app = QApplication(sys.argv) window = ProfessionalMeshGUI() window.show() sys.exit(app.exec()) -
AuthorPosts
Viewing 1 post (of 1 total)
- You must be logged in to reply to this topic.
