Quisk version 4.2.40

This commit is contained in:
James C. Ahlstrom
2024-11-15 16:39:47 -05:00
parent fb888a2e08
commit 7e8a0846f7
6 changed files with 105 additions and 50 deletions
+8
View File
@@ -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 Quisk Version 4.2.39 October 2024
================================== ==================================
I added an optional Hardware method PollGuiControl(self). If you define this in your Hardware file, I added an optional Hardware method PollGuiControl(self). If you define this in your Hardware file,
+1 -1
View File
@@ -1,2 +1,2 @@
#Quisk version 4.2.39 #Quisk version 4.2.40
from .quisk import main from .quisk import main
+87 -39
View File
@@ -1,9 +1,9 @@
from __future__ import absolute_import
# This code was contributed by Christof, DJ4CM. Many Thanks!! # This code was contributed by Christof, DJ4CM. Many Thanks!!
# Modified November 2024 by N2ADR to remove deprecated telnetlib module.
import threading import threading
import socket
import time import time
import telnetlib
import quisk_conf_defaults as conf import quisk_conf_defaults as conf
class DxEntry: class DxEntry:
@@ -81,61 +81,109 @@ class DxEntry:
class DxCluster(threading.Thread): class DxCluster(threading.Thread):
def __init__(self): def __init__(self):
self.do_init = 1 self.error = 'Starting'
self.dxSpots = []
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.doQuit = threading.Event() self.doQuit = threading.Event()
self.dxSpots = []
self.doQuit.clear() 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): def run(self):
self.telnetInit()
self.telnetConnect() self.telnetConnect()
if self.error:
self.sock.close()
return
while not self.doQuit.isSet(): while not self.doQuit.isSet():
try: try:
self.telnetRead() by = self.sock.recv(1024)
except TimeoutError:
continue
except: except:
self.tn.close() by = b''
time.sleep(20) 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(): if not self.doQuit.isSet():
self.telnetConnect() self.telnetConnect()
self.tn.close() if self.error:
self.sock.close()
def setListener (self, listener): return
self.listener = listener self.sock.close()
def telnetInit(self):
self.tn = telnetlib.Telnet()
def telnetConnect(self): 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): for i in range(10):
try: try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10) self.bytes += self.sock.recv(1024)
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
except: 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: if conf.dxClPassword:
self.tn.read_until(b"Password: ") for i in range(10):
self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n") 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): def Poll(self):
message = self.tn.read_until(b'\n', 60).decode(encoding='utf-8', errors='replace') with self.dxLock:
if self.doQuit.isSet() == False: index = self.bytes.find(b"\n")
dxEntry = DxEntry(); if index >= 0:
if dxEntry.parseMessage(message): message = self.bytes[0:index + 1]
for i, listElement in enumerate(self.dxSpots): self.bytes = self.bytes[index + 1:]
if (listElement.equal(dxEntry)): else:
listElement.join (dxEntry) return
return message = message.decode(encoding='utf-8', errors='replace')
if listElement.isExpired(): dxEntry = DxEntry()
del (self.dxSpots[i]) if dxEntry.parseMessage(message):
self.dxSpots.append(dxEntry) for i, listElement in enumerate(self.dxSpots):
if self.listener: if (listElement.equal(dxEntry)):
self.listener() listElement.join (dxEntry)
return True
def getHost(self): if listElement.isExpired():
return self.tn.host + ':' + str(self.tn.port) del (self.dxSpots[i])
self.dxSpots.append(dxEntry)
return True
def stop(self): def stop(self):
self.doQuit.set() 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
+2
View File
@@ -402,6 +402,8 @@ class Hardware(BaseHardware):
if do_tx: if do_tx:
try: try:
self.rx_udp_socket.send(s) self.rx_udp_socket.send(s)
if DEBUG:
self.PrintStatus(' Send', self.want_udp_status)
except: except:
pass pass
def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False): def SetVNA(self, key_down=None, vna_start=None, vna_stop=None, vna_count=None, do_tx=False):
+6 -9
View File
@@ -1434,12 +1434,8 @@ class ConfigStatus(wx.ScrolledCanvas):
self.MakeRow2("Sample interrupts", self.interrupts, cfile) self.MakeRow2("Sample interrupts", self.interrupts, cfile)
self.MakeRow2("Microphone or DGT level dB", level, qfile) self.MakeRow2("Microphone or DGT level dB", level, qfile)
self.MakeRow2("FFT number of points", self.fft_size, err_msg) self.MakeRow2("FFT number of points", self.fft_size, err_msg)
if conf.dxClHost: # connection to dx cluster if application.dxCluster:
nSpots = len(application.dxCluster.dxSpots) msg = application.dxCluster.dxStatus()
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
self.MakeRow2("FFT number of errors", self.fft_error, msg) self.MakeRow2("FFT number of errors", self.fft_error, msg)
else: else:
self.MakeRow2("FFT number of errors", self.fft_error) self.MakeRow2("FFT number of errors", self.fft_error)
@@ -3991,6 +3987,7 @@ class App(wx.App):
minh = 100 minh = 100
maxh = -1 maxh = -1
self.main_frame.SetSizeHints(minw, minh, maxw, maxh) 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() if hasattr(Hardware, 'pre_open'): # pre_open() is called before open()
Hardware.pre_open() Hardware.pre_open()
if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox): if self.local_conf.GetWidgets(self, Hardware, conf, frame, gbs, vertBox):
@@ -4030,7 +4027,6 @@ class App(wx.App):
if conf.dxClHost: if conf.dxClHost:
# create DX Cluster and register listener for change notification # create DX Cluster and register listener for change notification
self.dxCluster = dxcluster.DxCluster() self.dxCluster = dxcluster.DxCluster()
self.dxCluster.setListener(self.OnDxClChange)
self.dxCluster.start() self.dxCluster.start()
# Create shortcut keys for buttons # Create shortcut keys for buttons
if conf.button_layout == 'Large screen': if conf.button_layout == 'Large screen':
@@ -4381,8 +4377,6 @@ class App(wx.App):
conf.tx_ip, conf.tx_audio_port, conf.tx_ip, conf.tx_audio_port,
conf.mic_sample_rate, conf.mic_channel_I, conf.mic_channel_Q, conf.mic_sample_rate, conf.mic_channel_I, conf.mic_channel_Q,
0.7, conf.mic_playback_rate) 0.7, conf.mic_playback_rate)
def OnDxClChange(self):
self.station_screen.Refresh()
def OnIdle(self, event): def OnIdle(self, event):
if self.screen: if self.screen:
self.screen.OnIdle(event) self.screen.OnIdle(event)
@@ -6639,6 +6633,9 @@ class App(wx.App):
self.ChangeDisplayFrequency(tune - vfo, vfo) self.ChangeDisplayFrequency(tune - vfo, vfo)
self.FldigiPoll() self.FldigiPoll()
self.HamlibPoll() self.HamlibPoll()
if self.dxCluster:
if self.dxCluster.Poll():
self.station_screen.Refresh()
if self.timer - self.slowheart_time0 > 0.5: if self.timer - self.slowheart_time0 > 0.5:
self.slowheart_time0 = self.timer self.slowheart_time0 = self.timer
if self.w_phase: if self.w_phase:
+1 -1
View File
@@ -7,7 +7,7 @@ import os
# You must define the version here. A title string including # You must define the version here. A title string including
# the version will be written to __init__.py and read by quisk.py. # 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 = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version) fp.write("#Quisk version %s\n" % Version)