loader image

Meshtastic App || Custom || Linux

What makes us different from other similar websites? Forums Tech Meshtastic App || Custom || Linux

Viewing 1 post (of 1 total)
  • Author
    Posts
  • #8424
    thumbtak
    Moderator

    Save as something like this mesh.py, and run as something like this python3 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())
Viewing 1 post (of 1 total)
  • You must be logged in to reply to this topic.
TAKs Shack