aboutsummaryrefslogtreecommitdiffstats
path: root/sw/eval-clock-cw-tx/gui.py
diff options
context:
space:
mode:
Diffstat (limited to 'sw/eval-clock-cw-tx/gui.py')
-rwxr-xr-xsw/eval-clock-cw-tx/gui.py222
1 files changed, 222 insertions, 0 deletions
diff --git a/sw/eval-clock-cw-tx/gui.py b/sw/eval-clock-cw-tx/gui.py
new file mode 100755
index 0000000..4efde84
--- /dev/null
+++ b/sw/eval-clock-cw-tx/gui.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2021 Matthias P. Braendli
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import socket
+import serial
+from tkinter import *
+from tkinter import ttk
+from functools import partial
+import sys
+
+NUM_MACROS=5
+
+DEFAULT_MACROS=["CQ WCA de HB9HI HB9HI HB9HI pse K", "REF WCA HB-xxxxx", "12345667890123455678901234567890", "", ""]
+
+class GUI(Frame):
+ def __init__(self, root):
+ super().__init__(root)
+ self.root = root
+ self.create_widgets()
+ self.last_tx_freq = None
+ self.last_serial_message = bytes()
+ try:
+ self.serial = serial.Serial("/dev/ttyACM0", 115200, timeout=0)
+ print(f"Opened serial port {self.serial.name}")
+ except Exception as e:
+ self.serial = None
+ self.set_status(f"Serial port failed {e}")
+
+ self.root.after(1000, self.fetch_gqrx_freq)
+ self.root.after(1000, self.read_serial)
+
+ def read_serial(self):
+ if self.serial is not None:
+ try:
+ dat = self.serial.read(1)
+ if dat:
+ self.last_serial_message += dat
+ if self.last_serial_message.endswith(b"\n"):
+ print(f"< {self.last_serial_message.decode().strip()}")
+ self.set_status(f"Received: {self.last_serial_message.decode().strip()}")
+ self.last_serial_message = bytes()
+ except serial.SerialException as e:
+ self.set_status(f"Serial read failed: {e:!r}")
+ except Exception as e:
+ self.set_status(f"Serial read failed: {e:!r}")
+ self.root.after(10, self.read_serial)
+ else:
+ self.root.after(1000, self.read_serial)
+
+ def fetch_gqrx_freq(self):
+ gqrx_sock = socket.socket()
+ gqrx_sock.settimeout(100)
+ try:
+ gqrx_sock.connect(("127.0.0.1", 7356))
+ gqrx_sock.send(b"f\n")
+ buf = gqrx_sock.recv(1024)
+
+ freq = int(buf.decode())
+ if self.enable_freq_sync.get():
+ self.gqrx_freq_var.set(freq)
+
+ tx_freq = freq + self.gqrx_offset_var.get()
+
+ if self.last_tx_freq != tx_freq:
+ self.last_tx_freq = tx_freq
+ self.send_serial('f{}\n'.format(tx_freq).encode("ascii"))
+
+ self.root.after(100, self.fetch_gqrx_freq)
+ except Exception as e:
+ self.set_status(f"GQRX socket error {e}")
+ self.root.after(1000, self.fetch_gqrx_freq)
+
+ gqrx_sock.close()
+
+ def terminate(self):
+ print("Terminate: send RX")
+ self.send_serial(b'rx\n')
+
+ def load_macro(self, i):
+ self.tx_message.set(self.macro_widgets[i]["var"].get())
+
+ def send_serial(self, message):
+ if self.serial is not None:
+ self.serial.write(message)
+
+ def set_font_narrow(self):
+ print("Set font narrow")
+ self.send_serial(b'fontn\n')
+
+ def set_font_wide(self):
+ print("Set font wide")
+ self.send_serial(b'fontw\n')
+
+ def send_tx_message(self):
+ msg = self.tx_message.get()
+ print(f"Send '{msg}'")
+ self.send_serial('m{}\n'.format(msg).encode("ascii"))
+
+ def toggle_tx_enabled(self):
+ transmit = self.tx_enabled_var.get()
+ if transmit:
+ print("Enable TX")
+ self.send_serial(b'tx\n')
+ else:
+ print("Disable TX")
+ self.send_serial(b'rx\n')
+
+ def create_widgets(self):
+ self.root.columnconfigure(0, weight=1)
+ self.root.rowconfigure(0, weight=1)
+ self.top_frame = Frame(self.root, borderwidth=5, relief="ridge", width=800, height=600)
+ self.top_frame.grid(column=0, row=0, sticky=(N, S, E, W))
+ self.top_frame.columnconfigure(0, weight=1)
+ self.top_frame.columnconfigure(1, weight=1)
+ self.top_frame.rowconfigure(0, weight=0)
+ self.top_frame.rowconfigure(1, weight=1)
+
+ self.macros_frame = Frame(self.top_frame, borderwidth=1, relief="ridge")
+ self.macros_frame.grid(column=0, row=0, sticky=(N, S, E, W))
+
+ self.macro_widgets = []
+ for i in range(NUM_MACROS):
+ tv = StringVar()
+ tv.set(DEFAULT_MACROS[i])
+ e = Entry(self.macros_frame, textvariable=tv, width=80)
+ e.grid(column=0, row=i)
+ b = Button(self.macros_frame, text=f"Load {i}", command=partial(self.load_macro, i))
+ b.grid(column=1, row=i)
+
+ self.macro_widgets.append({'entry': e, 'button': b, 'var': tv})
+
+
+ self.control_frame = Frame(self.top_frame, borderwidth=1, relief="ridge")
+ self.control_frame.grid(column=1, row=0, sticky=(N, S, E, W))
+
+ self.font_frame = Frame(self.control_frame, borderwidth=1, relief="ridge")
+ self.font_frame.grid(column=0, row=0)
+ self.font_label = Label(self.font_frame, text="Font selection")
+ self.font_label.grid(column=0, row=0)
+ self.button_font_narrow = Button(self.font_frame, text="Narrow", command=self.set_font_narrow)
+ self.button_font_narrow.grid(column=0, row=1)
+ self.button_font_wide = Button(self.font_frame, text="Wide", command=self.set_font_wide)
+ self.button_font_wide.grid(column=1, row=1)
+
+ self.gqrx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge")
+ self.gqrx_frame.grid(column=0, row=1, sticky=(N, S, E, W))
+ self.enable_freq_sync = IntVar()
+ self.gqrx_checkbutton = Checkbutton(self.gqrx_frame, text="Sync", variable=self.enable_freq_sync)
+ self.gqrx_checkbutton.grid(column=0, row=0, columnspan=1)
+
+ self.gqrx_freq_label = Label(self.gqrx_frame, text="Frequency")
+ self.gqrx_freq_label.grid(column=0, row=1)
+ self.gqrx_freq_var = IntVar()
+ self.gqrx_freq = Label(self.gqrx_frame, width=10, textvariable=self.gqrx_freq_var)
+ self.gqrx_freq.grid(column=1, row=1, columnspan=1, sticky=(N, S, E, W))
+
+ self.gqrx_offset_label = Label(self.gqrx_frame, text="Offset")
+ self.gqrx_offset_label.grid(column=0, row=2)
+ self.gqrx_offset_var = IntVar()
+ self.gqrx_offset_var.set(800)
+ self.gqrx_offset = Entry(self.gqrx_frame, width=10, textvariable=self.gqrx_offset_var)
+ self.gqrx_offset.grid(column=1, row=2, columnspan=1, sticky=(N, S, E, W))
+
+
+ self.tx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge")
+ self.tx_frame.grid(column=0, row=2, sticky=(N, S, E, W))
+ self.tx_enabled_var = IntVar()
+ self.tx_checkbutton = Checkbutton(self.tx_frame, text="Transmit", variable=self.tx_enabled_var, command=self.toggle_tx_enabled)
+ self.tx_checkbutton.grid(column=0, row=0, columnspan=2)
+
+ self.transmit_frame = Frame(self.top_frame, borderwidth=1, relief="ridge")
+ self.transmit_frame.grid(column=0, row=1, columnspan=2)
+ self.transmit_frame.columnconfigure(0, weight=1)
+ self.transmit_frame.columnconfigure(1, weight=1)
+
+ self.tx_message = StringVar()
+ self.tx_message_entry = Entry(self.transmit_frame, width=90, textvariable=self.tx_message)
+ self.tx_message_entry.grid(column=0, row=0, sticky=(N, S, E, W))
+ self.tx_message_entry.columnconfigure(0, weight=1)
+ self.tx_message_entry.columnconfigure(1, weight=1)
+ self.tx_message_button = Button(self.transmit_frame, text="Send", command=self.send_tx_message)
+ self.tx_message_button.grid(column=0, row=1, sticky=(N, S, E, W))
+
+ self.status_var = StringVar()
+ self.status_label = Label(self.top_frame, bd=1, relief=SUNKEN, anchor=W, textvariable=self.status_var)
+ self.status_label.grid(column=0, row=2, columnspan=2, sticky=(N, S, E, W))
+ self.set_status("")
+
+ def set_status(self, message):
+ self.status_var.set(f"Status: {message}")
+
+
+root = Tk()
+
+app = GUI(root)
+
+try:
+ root.mainloop()
+finally:
+ app.terminate()
+