From 7e8a0846f790f58a7885e631fb5d3aee3db27841 Mon Sep 17 00:00:00 2001 From: "James C. Ahlstrom" Date: Fri, 15 Nov 2024 16:39:47 -0500 Subject: [PATCH] Quisk version 4.2.40 --- CHANGELOG.txt | 8 +++ __init__.py | 2 +- dxcluster.py | 126 +++++++++++++++++++++++++++------------ hiqsdr/quisk_hardware.py | 2 + quisk.py | 15 ++--- setup.py | 2 +- 6 files changed, 105 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b37413e..1b41599 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,11 @@ +Quisk Version 4.2.40 November 2024 +=================================== +Python 3.13 eliminated the telnetlib module, so I wrote a replacement. I fixed a bug in window sizing. +If you use the Dx Cluster feature, please report any problems. Dx Cluster used to use the telnetlib module +but is now new code. + +I updated the install to Python 3.13. Please report any problems with Python 3.13. + Quisk Version 4.2.39 October 2024 ================================== I added an optional Hardware method PollGuiControl(self). If you define this in your Hardware file, diff --git a/__init__.py b/__init__.py index 51359a3..4b6958f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,2 +1,2 @@ -#Quisk version 4.2.39 +#Quisk version 4.2.40 from .quisk import main diff --git a/dxcluster.py b/dxcluster.py index eff52d7..5bd915a 100644 --- a/dxcluster.py +++ b/dxcluster.py @@ -1,9 +1,9 @@ -from __future__ import absolute_import # This code was contributed by Christof, DJ4CM. Many Thanks!! +# Modified November 2024 by N2ADR to remove deprecated telnetlib module. import threading +import socket import time -import telnetlib import quisk_conf_defaults as conf class DxEntry: @@ -81,61 +81,109 @@ class DxEntry: class DxCluster(threading.Thread): def __init__(self): - self.do_init = 1 + self.error = 'Starting' + self.dxSpots = [] threading.Thread.__init__(self) self.doQuit = threading.Event() - self.dxSpots = [] self.doQuit.clear() + self.dxLock = threading.Lock() + self.addr = conf.dxClHost + ':' + str(conf.dxClPort) + self.msg_no_spots = "No DX Cluster data from " + self.addr + self.msg_one_spot = '1 DX spot received from ' + self.addr + self.msg_spots = ' DX spots received from ' + self.addr def run(self): - self.telnetInit() self.telnetConnect() + if self.error: + self.sock.close() + return while not self.doQuit.isSet(): try: - self.telnetRead() + by = self.sock.recv(1024) + except TimeoutError: + continue except: - self.tn.close() - time.sleep(20) + by = b'' + if by: + with self.dxLock: + self.bytes += by + else: + self.sock.close() + self.error = "Restarting " + self.addr + time.sleep(2) if not self.doQuit.isSet(): self.telnetConnect() - self.tn.close() - - def setListener (self, listener): - self.listener = listener + if self.error: + self.sock.close() + return + self.sock.close() - def telnetInit(self): - self.tn = telnetlib.Telnet() - def telnetConnect(self): + self.bytes = bytearray(0) + self.error = 'Starting' + self.sock = socket.socket() + self.sock.settimeout(20) + try: + self.sock.connect( (conf.dxClHost, conf.dxClPort) ) + except: + self.error = "Failed to connect to " + self.addr + return + self.sock.settimeout(1) for i in range(10): try: - self.tn.open(conf.dxClHost, conf.dxClPort, 10) - self.tn.read_until(b"login:", 10) - self.tn.write(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\n") # user_call_sign may be Unicode - break + self.bytes += self.sock.recv(1024) except: - time.sleep(0.5) + pass + if b"login:" in self.bytes: + break + else: + self.error = 'No "login:" prompt from ' + self.addr + return + self.sock.sendall(conf.user_call_sign.encode('utf-8', errors='ignore') + b"\r\n") if conf.dxClPassword: - self.tn.read_until(b"Password: ") - self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n") + for i in range(10): + try: + self.bytes += self.sock.recv(1024) + except: + pass + if b"Password:" in self.bytes: + break + else: + self.error = 'No "Password:" prompt from ' + self.addr + return + self.sock.sendall(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\r\n") + self.error = '' + self.bytes = bytearray(0) - def telnetRead(self): - message = self.tn.read_until(b'\n', 60).decode(encoding='utf-8', errors='replace') - if self.doQuit.isSet() == False: - dxEntry = DxEntry(); - if dxEntry.parseMessage(message): - for i, listElement in enumerate(self.dxSpots): - if (listElement.equal(dxEntry)): - listElement.join (dxEntry) - return - if listElement.isExpired(): - del (self.dxSpots[i]) - self.dxSpots.append(dxEntry) - if self.listener: - self.listener() - - def getHost(self): - return self.tn.host + ':' + str(self.tn.port) + def Poll(self): + with self.dxLock: + index = self.bytes.find(b"\n") + if index >= 0: + message = self.bytes[0:index + 1] + self.bytes = self.bytes[index + 1:] + else: + return + message = message.decode(encoding='utf-8', errors='replace') + dxEntry = DxEntry() + if dxEntry.parseMessage(message): + for i, listElement in enumerate(self.dxSpots): + if (listElement.equal(dxEntry)): + listElement.join (dxEntry) + return True + if listElement.isExpired(): + del (self.dxSpots[i]) + self.dxSpots.append(dxEntry) + return True def stop(self): self.doQuit.set() + + def dxStatus(self): + if self.error: + return self.error + nSpots = len(self.dxSpots) + if nSpots == 0: + return self.msg_no_spots + elif nSpots == 1: + return self.msg_one_spot + return str(nSpots) + self.msg_spots diff --git a/hiqsdr/quisk_hardware.py b/hiqsdr/quisk_hardware.py index 2e18590..907b9ad 100644 --- a/hiqsdr/quisk_hardware.py +++ b/hiqsdr/quisk_hardware.py @@ -402,6 +402,8 @@ class Hardware(BaseHardware): if do_tx: try: self.rx_udp_socket.send(s) + if DEBUG: + self.PrintStatus(' Send', self.want_udp_status) except: pass def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False): diff --git a/quisk.py b/quisk.py index 32951bd..fc139a9 100644 --- a/quisk.py +++ b/quisk.py @@ -1434,12 +1434,8 @@ class ConfigStatus(wx.ScrolledCanvas): self.MakeRow2("Sample interrupts", self.interrupts, cfile) self.MakeRow2("Microphone or DGT level dB", level, qfile) self.MakeRow2("FFT number of points", self.fft_size, err_msg) - if conf.dxClHost: # connection to dx cluster - nSpots = len(application.dxCluster.dxSpots) - if nSpots > 0: - msg = str(nSpots) + ' DX spot' + ('' if nSpots==1 else 's') + ' received from ' + application.dxCluster.getHost() - else: - msg = "No DX Cluster data from %s" % conf.dxClHost + if application.dxCluster: + msg = application.dxCluster.dxStatus() self.MakeRow2("FFT number of errors", self.fft_error, msg) else: self.MakeRow2("FFT number of errors", self.fft_error) @@ -3991,6 +3987,7 @@ class App(wx.App): minh = 100 maxh = -1 self.main_frame.SetSizeHints(minw, minh, maxw, maxh) + self.main_frame.SetClientSize(wx.Size(width, self.height)) if hasattr(Hardware, 'pre_open'): # pre_open() is called before open() Hardware.pre_open() if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox): @@ -4030,7 +4027,6 @@ class App(wx.App): if conf.dxClHost: # create DX Cluster and register listener for change notification self.dxCluster = dxcluster.DxCluster() - self.dxCluster.setListener(self.OnDxClChange) self.dxCluster.start() # Create shortcut keys for buttons if conf.button_layout == 'Large screen': @@ -4381,8 +4377,6 @@ class App(wx.App): conf.tx_ip, conf.tx_audio_port, conf.mic_sample_rate, conf.mic_channel_I, conf.mic_channel_Q, 0.7, conf.mic_playback_rate) - def OnDxClChange(self): - self.station_screen.Refresh() def OnIdle(self, event): if self.screen: self.screen.OnIdle(event) @@ -6639,6 +6633,9 @@ class App(wx.App): self.ChangeDisplayFrequency(tune - vfo, vfo) self.FldigiPoll() self.HamlibPoll() + if self.dxCluster: + if self.dxCluster.Poll(): + self.station_screen.Refresh() if self.timer - self.slowheart_time0 > 0.5: self.slowheart_time0 = self.timer if self.w_phase: diff --git a/setup.py b/setup.py index 1eff385..fe45cc1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ import os # You must define the version here. A title string including # the version will be written to __init__.py and read by quisk.py. -Version = '4.2.39' +Version = '4.2.40' fp = open("__init__.py", "w") # write title string fp.write("#Quisk version %s\n" % Version)