Skip to content

Commit 1337c9e

Browse files
committed
gui messages
1 parent 759013f commit 1337c9e

File tree

3 files changed

+278
-8
lines changed

3 files changed

+278
-8
lines changed

Source/GUI/Installation.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ python -m venv /path/to/myenv
3030
source venv/bin/activate # macOS/Linux
3131
.\venv\Scripts\activate # Windows
3232

33-
deactivate # Exits the environment
33+
deactivate # Exits the environment
34+
35+
36+
37+
pip install PySide6

Source/GUI/Main.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,10 @@ def change_filter_mode(*args):
680680
filter_mode = filter_var.get()
681681
update_log_display()
682682

683-
# ---------- GUI ----------
683+
load_config()
684+
685+
#region GUI Main
686+
684687
root = tk.Tk()
685688
#root.iconbitmap("ham_messenger_icon.ico")
686689
root.title("HamMessenger Serial GUI")
@@ -700,33 +703,42 @@ def change_filter_mode(*args):
700703
tabs.add(log_tab, text="Log")
701704
tabs.add(map_tab, text="Map")
702705

703-
load_config()
706+
#endregion
707+
708+
#region Connection Controls Bar
704709

705-
# Control Bar
706710
control_frame = tk.Frame(root)
707711
control_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
708712

713+
# add port label and combobox
709714
tk.Label(control_frame, text="Port:").grid(row=0, column=0)
710715
port_var = tk.StringVar()
711716
port_menu = ttk.Combobox(control_frame, textvariable=port_var, width=12)
712717
port_menu.grid(row=0, column=1, sticky="w")
713718

719+
# add refresh button
714720
refresh_btn = tk.Button(control_frame, text="Refresh", command=refresh_ports)
715721
refresh_btn.grid(row=0, column=2, padx=(5, 10))
716722

723+
# add baud rate label and combobox
717724
tk.Label(control_frame, text="Baud:").grid(row=0, column=3)
718725
baud_var = tk.StringVar(value=config.get("baud", "115200"))
719726
baud_menu = ttk.Combobox(control_frame, textvariable=baud_var,
720727
values=["9600", "19200", "38400", "57600", "115200"], width=10)
721728
baud_menu.grid(row=0, column=4)
722729

730+
# add connect button
723731
connect_btn = tk.Button(control_frame, text="Connect", command=connect_serial)
724732
connect_btn.grid(row=0, column=5, padx=(10, 0))
725733

734+
# add connection status label
726735
status_label = tk.Label(control_frame, text="Not connected", fg="red")
727736
status_label.grid(row=0, column=6, padx=(10, 0))
728737

729-
# Quick Commands
738+
#endregion
739+
740+
#region Commands Bar
741+
730742
quick_frame = tk.Frame(root)
731743
quick_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=(0, 5))
732744

@@ -750,7 +762,10 @@ def add_quick_command():
750762
add_quick_btn = tk.Button(quick_frame, text="Add", command=add_quick_command)
751763
add_quick_btn.pack(side=tk.LEFT, padx=(5, 0))
752764

753-
# Log Controls
765+
#endregion
766+
767+
#region Log Controls Bar
768+
754769
log_control_frame = tk.Frame(root)
755770
log_control_frame.grid(row=3, column=0, sticky="ew", padx=10)
756771

@@ -770,15 +785,21 @@ def add_quick_command():
770785
open_log_btn = tk.Button(log_control_frame, text="Open Log", command=open_log_file)
771786
open_log_btn.pack(side=tk.LEFT, padx=(10, 0))
772787

773-
# Log Viewer
788+
#endregion
789+
790+
#region Log Viewer Tab
791+
774792
log_box = scrolledtext.ScrolledText(log_tab, wrap=tk.WORD, state=tk.DISABLED)
775793
log_box.grid(row=0, column=0, sticky="nsew", padx=10, pady=10, in_=log_tab)
776794
log_box.tag_config("Sent", foreground="blue")
777795
log_box.tag_config("Received", foreground="green")
778796
log_box.tag_config("Modem", foreground="orange")
779797
log_box.tag_config("SD", foreground="red")
780798

781-
# Map Viewer
799+
#endregion
800+
801+
#region Map Viewer Tab
802+
782803
map_widget = TkinterMapView(map_tab, width=900, height=500)
783804
map_widget.pack(fill="both", expand=True)
784805
map_widget.set_position(37.7749, -122.4194) # Default to SF
@@ -811,6 +832,12 @@ def show_raw_data(raw: str, marker):
811832
label.pack(padx=4, pady=2)
812833
popup.after(3000, popup.destroy)
813834

835+
#endregion
836+
837+
#region Messages Tab
838+
839+
#endregion
840+
814841
# Startup
815842
refresh_ports()
816843
if config.get("port"):

Source/GUI/Test3.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import sys
2+
import serial
3+
import serial.tools.list_ports
4+
from PySide6.QtWidgets import (
5+
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTabWidget,
6+
QLabel, QComboBox, QPushButton, QPlainTextEdit, QLineEdit, QFileDialog,
7+
QMessageBox
8+
)
9+
from PySide6.QtCore import Qt, QTimer, QThread, Signal, QUrl
10+
from PySide6.QtWebEngineWidgets import QWebEngineView
11+
from datetime import datetime
12+
import json
13+
import os
14+
import platform
15+
import re
16+
import base64
17+
from queue import Queue
18+
19+
# Placeholder for APRS functionality
20+
from aprspy import APRS
21+
22+
# Global message queue
23+
message_queue = Queue()
24+
25+
class SerialThread(QThread):
26+
message_received = Signal(str)
27+
28+
def __init__(self, port, baudrate):
29+
super().__init__()
30+
self.port = port
31+
self.baudrate = baudrate
32+
self.running = False
33+
self.serial_connection = None
34+
35+
def run(self):
36+
try:
37+
self.serial_connection = serial.Serial(self.port, self.baudrate, timeout=1)
38+
self.running = True
39+
while self.running:
40+
if self.serial_connection.in_waiting:
41+
line = self.serial_connection.readline().decode('utf-8', errors='replace').strip()
42+
if line:
43+
self.message_received.emit(line)
44+
except serial.SerialException as e:
45+
self.message_received.emit(f"Serial error: {e}")
46+
47+
def stop(self):
48+
self.running = False
49+
if self.serial_connection and self.serial_connection.is_open:
50+
self.serial_connection.close()
51+
52+
def write(self, data):
53+
if self.serial_connection and self.serial_connection.is_open:
54+
self.serial_connection.write(data.encode('utf-8'))
55+
56+
class HamMessengerGUI(QMainWindow):
57+
def __init__(self):
58+
super().__init__()
59+
self.setWindowTitle("HamMessenger Serial GUI")
60+
self.resize(950, 620)
61+
self.serial_thread = None
62+
self.aprs_parser = APRS()
63+
64+
self.central_widget = QWidget()
65+
self.setCentralWidget(self.central_widget)
66+
self.main_layout = QVBoxLayout(self.central_widget)
67+
68+
self.create_control_panel()
69+
self.create_command_panel()
70+
self.create_tabs()
71+
self.populate_serial_ports()
72+
73+
def create_control_panel(self):
74+
control_row = QHBoxLayout()
75+
76+
control_row.addWidget(QLabel("Port:"))
77+
self.port_combo = QComboBox()
78+
control_row.addWidget(self.port_combo)
79+
80+
refresh_button = QPushButton("Refresh")
81+
refresh_button.clicked.connect(self.populate_serial_ports)
82+
control_row.addWidget(refresh_button)
83+
84+
control_row.addWidget(QLabel("Baud:"))
85+
self.baud_combo = QComboBox()
86+
self.baud_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
87+
self.baud_combo.setCurrentText("115200")
88+
control_row.addWidget(self.baud_combo)
89+
90+
self.connect_button = QPushButton("Connect")
91+
self.connect_button.clicked.connect(self.toggle_connection)
92+
control_row.addWidget(self.connect_button)
93+
94+
self.status_label = QLabel("Not connected")
95+
control_row.addWidget(self.status_label)
96+
97+
self.main_layout.addLayout(control_row)
98+
99+
def create_command_panel(self):
100+
cmd_row = QHBoxLayout()
101+
102+
cmd_row.addWidget(QLabel("Command:"))
103+
self.command_input = QLineEdit()
104+
self.command_input.returnPressed.connect(self.send_serial_command)
105+
cmd_row.addWidget(self.command_input)
106+
107+
self.send_button = QPushButton("Send")
108+
self.send_button.clicked.connect(self.send_serial_command)
109+
cmd_row.addWidget(self.send_button)
110+
111+
self.main_layout.addLayout(cmd_row)
112+
113+
def create_tabs(self):
114+
self.tabs = QTabWidget()
115+
116+
self.log_tab = QWidget()
117+
self.log_layout = QVBoxLayout(self.log_tab)
118+
self.log_output = QPlainTextEdit()
119+
self.log_output.setReadOnly(True)
120+
self.log_layout.addWidget(self.log_output)
121+
self.tabs.addTab(self.log_tab, "Log")
122+
123+
self.map_tab = QWidget()
124+
self.map_layout = QVBoxLayout(self.map_tab)
125+
self.map_view = QWebEngineView()
126+
self.leaflet_script = """
127+
var map = L.map('map').setView([37.7749, -122.4194], 10);
128+
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
129+
maxZoom: 19,
130+
attribution: '© OpenStreetMap contributors'
131+
}).addTo(map);
132+
var markers = [];
133+
function addMarker(lat, lon, label) {
134+
var marker = L.marker([lat, lon]).addTo(map).bindPopup(label);
135+
markers.push(marker);
136+
}
137+
"""
138+
leaflet_html = f"""
139+
<!DOCTYPE html>
140+
<html>
141+
<head>
142+
<meta charset='utf-8' />
143+
<title>Leaflet Map</title>
144+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
145+
<link rel='stylesheet' href='https://unpkg.com/[email protected]/dist/leaflet.css' />
146+
<script src='https://unpkg.com/[email protected]/dist/leaflet.js'></script>
147+
<style>html, body, #map {{ height: 100%; margin: 0; }}</style>
148+
</head>
149+
<body>
150+
<div id='map'></div>
151+
<script>{self.leaflet_script}</script>
152+
</body>
153+
</html>
154+
"""
155+
self.map_view.setHtml(leaflet_html)
156+
self.map_layout.addWidget(self.map_view)
157+
self.tabs.addTab(self.map_tab, "Map")
158+
159+
self.msg_tab = QWidget()
160+
self.msg_layout = QVBoxLayout(self.msg_tab)
161+
self.msg_output = QPlainTextEdit()
162+
self.msg_output.setReadOnly(True)
163+
self.msg_layout.addWidget(self.msg_output)
164+
self.tabs.addTab(self.msg_tab, "Messages")
165+
166+
self.main_layout.addWidget(self.tabs)
167+
168+
def populate_serial_ports(self):
169+
current_selection = self.port_combo.currentData()
170+
self.port_combo.clear()
171+
ports = serial.tools.list_ports.comports()
172+
for port in ports:
173+
desc = f"{port.device}{port.description}"
174+
self.port_combo.addItem(desc, port.device)
175+
176+
# Try to restore previous selection
177+
for i in range(self.port_combo.count()):
178+
if self.port_combo.itemData(i) == current_selection:
179+
self.port_combo.setCurrentIndex(i)
180+
break
181+
182+
def toggle_connection(self):
183+
if self.serial_thread and self.serial_thread.isRunning():
184+
self.serial_thread.stop()
185+
self.serial_thread.quit()
186+
self.serial_thread.wait()
187+
self.serial_thread = None
188+
self.connect_button.setText("Connect")
189+
self.status_label.setText("Disconnected")
190+
else:
191+
port = self.port_combo.currentData()
192+
baudrate = int(self.baud_combo.currentText())
193+
if not port:
194+
QMessageBox.warning(self, "Error", "No port selected")
195+
return
196+
self.serial_thread = SerialThread(port, baudrate)
197+
self.serial_thread.message_received.connect(self.handle_serial_data)
198+
self.serial_thread.start()
199+
self.connect_button.setText("Disconnect")
200+
self.status_label.setText(f"Connected to {port}")
201+
202+
def send_serial_command(self):
203+
text = self.command_input.text().strip()
204+
if not text:
205+
return
206+
if self.serial_thread and self.serial_thread.isRunning():
207+
self.serial_thread.write(text + "\n")
208+
self.command_input.clear()
209+
else:
210+
QMessageBox.warning(self, "Not Connected", "No serial connection is active.")
211+
212+
def handle_serial_data(self, line):
213+
timestamp = datetime.now().strftime("[%H:%M:%S]")
214+
self.log_output.appendPlainText(f"{timestamp} {line}")
215+
216+
# Attempt to parse APRS
217+
try:
218+
parsed = self.aprs_parser.parse(line)
219+
if parsed:
220+
message_queue.put(parsed)
221+
formatted = json.dumps(parsed, indent=2)
222+
self.msg_output.appendPlainText(f"{timestamp} APRS: {formatted}")
223+
224+
# Plot on map if lat/lon present
225+
lat = parsed.get("latitude")
226+
lon = parsed.get("longitude")
227+
src = parsed.get("source")
228+
if lat and lon:
229+
js = f"addMarker({lat}, {lon}, '{src}');"
230+
self.map_view.page().runJavaScript(js)
231+
232+
except Exception as e:
233+
self.msg_output.appendPlainText(f"{timestamp} Failed to parse APRS: {e}")
234+
235+
if __name__ == "__main__":
236+
app = QApplication(sys.argv)
237+
window = HamMessengerGUI()
238+
window.show()
239+
sys.exit(app.exec())

0 commit comments

Comments
 (0)