#!/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 os 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()}") if self.last_serial_message == b"Switch RX\n": os.system("pactl set-sink-mute 0 0") elif self.last_serial_message == b"Switch TX\n": os.system("pactl set-sink-mute 0 1") elif self.last_serial_message.startswith(b"FH sent"): self.send_serial(b'rx\n') 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) # For GQRX remote control commands see https://github.com/csete/gqrx/blob/master/resources/remote-control.txt 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.toggle_to_rx() def toggle_to_rx(self): 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"TX and Send '{msg}'") self.send_serial('tx\nm{}\n'.format(msg).encode("ascii")) 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_button = Button(self.tx_frame, text="Receive!", command=self.toggle_to_rx) self.tx_button.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()