diff options
Diffstat (limited to 'sw/eval-clock-cw-tx/gui.py')
-rwxr-xr-x | sw/eval-clock-cw-tx/gui.py | 222 |
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() + |