Quisk version 4.2.42
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
Quisk Version 4.2.42 April 2025
|
||||
================================
|
||||
I fixed a bug in the Kenwood IF CAT command. Thanks Nick! I moved the Pulse server IP address to the Sound screen.
|
||||
I added a CAT method to imitate the Elecraft K4 command set over a TCP port. Quisk now has four CAT controls. To
|
||||
make this clearer I added more documentation to the Remote screen.
|
||||
|
||||
Quisk Version 4.2.41 January 2025
|
||||
==================================
|
||||
I made some improvements to the top-level Config/Radios screen. There is now an option to
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#Quisk version 4.2.41
|
||||
#Quisk version 4.2.42
|
||||
from .quisk import main
|
||||
|
||||
+104
-20
@@ -1338,7 +1338,16 @@ class RadioNotebook(wx.Notebook): # The second-level notebook for each radio nam
|
||||
self.AddPage(page, "Sound")
|
||||
self.pages.append(page)
|
||||
for section, names in local_conf.sections:
|
||||
if section in ('Sound', 'Bands', 'Filters'): # There is a special page for these sections
|
||||
if section == 'Sound': # There is a special page for these sections
|
||||
continue
|
||||
if section == 'Bands':
|
||||
continue
|
||||
if section == 'Filters':
|
||||
continue
|
||||
if section == 'Remote':
|
||||
page = RadioRemote(self, radio_name, section, names)
|
||||
self.AddPage(page, section)
|
||||
self.pages.append(page)
|
||||
continue
|
||||
page = RadioSection(self, radio_name, section, names)
|
||||
self.AddPage(page, section)
|
||||
@@ -1979,6 +1988,16 @@ class ControlMixin:
|
||||
btn.SetSizeHints(h, h, h, h)
|
||||
self.gbs.Add(btn, (self.row, col), flag=wx.ALIGN_CENTER_VERTICAL|wx.RIGHT, border=self.charx*border)
|
||||
return sld, btn
|
||||
def AddTextHeading(self, key, text, help_text):
|
||||
if key in text: # Start of heading
|
||||
self.NextRow()
|
||||
self.SetFont(self.heading_font)
|
||||
self.AddTextL(1, help_text, span=7)
|
||||
self.SetFont(self.font)
|
||||
self.NextRow()
|
||||
self.col = 2
|
||||
return key
|
||||
return None
|
||||
def AddPopupMenuHelp(self, Qclass, col, text, btn_text, help_text, span1=1, span2=1, border=1):
|
||||
if text:
|
||||
txt = wx.StaticText(self, -1, text)
|
||||
@@ -2890,16 +2909,7 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
|
||||
radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||
radio_type = radio_dict['hardware_file_type']
|
||||
for name, text, fmt, help_text, values in self.names:
|
||||
if name == 'remote_radio_password':
|
||||
self.AddTextButtonHelp(col, text, "Change", self.OnChangePassword, help_text)
|
||||
if col == 1:
|
||||
col = 4
|
||||
else:
|
||||
col = 1
|
||||
self.NextRow()
|
||||
elif name == 'remote_radio_ip' and radio_type != "Control Head":
|
||||
continue
|
||||
elif name == 'favorites_file_path':
|
||||
if name == 'favorites_file_path':
|
||||
self.favorites_path = radio_dict.get('favorites_file_path', '')
|
||||
row = self.row
|
||||
self.row = 1
|
||||
@@ -3087,14 +3097,6 @@ class RadioSection(BaseWindow): # The pages for each section in the second-leve
|
||||
index = self.list_ctrl.GetFirstSelected()
|
||||
if index >= 0:
|
||||
self.list_ctrl.Select(index, 0)
|
||||
def OnChangePassword(self, event):
|
||||
text = local_conf.globals.get('remote_radio_password', '')
|
||||
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
|
||||
"Password Entry", text)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
local_conf.globals["remote_radio_password"] = dlg.GetValue()
|
||||
local_conf.settings_changed = True
|
||||
local_conf.SaveState()
|
||||
|
||||
class RadioHardwareBase(BaseWindow): # The Hardware page in the second-level notebook for each radio
|
||||
def __init__(self, parent, radio_name):
|
||||
@@ -3747,7 +3749,7 @@ class RadioSound(BaseWindow): # The Sound page in the second-level notebook for
|
||||
self.radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||
self.num_cols = 8
|
||||
for name, text, fmt, help_text, values in local_conf.GetSectionData('Sound'):
|
||||
if name in ('digital_output_level', 'file_play_level'):
|
||||
if name in ('digital_output_level', 'file_play_level', 'IQ_Server_IP'):
|
||||
value = self.GetValue(name, self.radio_dict)
|
||||
no_edit = "choice" in fmt or fmt == 'boolean'
|
||||
txt, cb, btn = self.AddTextComboHelp(1, text, value, values, help_text, no_edit)
|
||||
@@ -4182,6 +4184,88 @@ The Tx bits are sent as the "Alex" filters and are sent to the J16 interface.'
|
||||
if hasattr(application.Hardware, "ImmediateChange"):
|
||||
application.Hardware.ImmediateChange(name)
|
||||
|
||||
class RadioRemote(BaseWindow): # The Remote page in the second-level notebook for each radio
|
||||
def __init__(self, parent, radio_name, section, names):
|
||||
BaseWindow.__init__(self, parent)
|
||||
self.radio_name = radio_name
|
||||
self.section = section
|
||||
self.names = names
|
||||
self.heading_font = wx.Font(conf.config_font_size, wx.FONTFAMILY_SWISS, wx.ITALIC,
|
||||
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
|
||||
self.MakeControls()
|
||||
def MakeControls(self):
|
||||
self.num_cols = 8
|
||||
#self.MarkCols()
|
||||
self.AddColSpacer(1, 3)
|
||||
self.NextRow()
|
||||
self.col = 2
|
||||
radio_dict = local_conf.GetRadioDict(self.radio_name)
|
||||
radio_type = radio_dict['hardware_file_type']
|
||||
heading = None
|
||||
for name, text, fmt, help_text, values in self.names:
|
||||
if heading:
|
||||
if heading not in text: # End of heading
|
||||
heading = None
|
||||
self.NextRow()
|
||||
self.col = 2
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("Hamlib Rig 2", text,
|
||||
'Rig control commands using "Hamlib Rig 2" are sent to a Quisk TCP port from multiple clients.')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("XML-RPC", text,
|
||||
'XML-RPC is a protocol that allows clients to call methods on a remote server using XML passed via HTTP')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("CAT serial", text,
|
||||
'"CAT serial" supports the FlexRadio PowerSDR 2.x command set sent over a serial port. Some Kenwood commands are included.')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("remote radio", text,
|
||||
'Quisk can be used as a control head to control a real radio located remotely. This feature is due to AC2YD.')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("K4 TCP", text,
|
||||
'Rig control commands using "K4 TCP" use the Elecraft K4 command set, and are sent to a Quisk TCP port from multiple clients.')
|
||||
if not heading:
|
||||
heading = self.AddTextHeading("Dx cluster", text,
|
||||
'"Dx Cluster" is a telnet service used to announce Dx spots in real time.')
|
||||
if name == 'remote_radio_password':
|
||||
self.AddTextButtonHelp(self.col, text, "Change", self.OnChangePassword, help_text)
|
||||
if self.col == 2:
|
||||
self.col = 5
|
||||
else:
|
||||
self.col = 2
|
||||
self.NextRow()
|
||||
elif name == 'remote_radio_ip' and radio_type != "Control Head":
|
||||
continue
|
||||
else:
|
||||
if fmt[0:4] in ('dict', 'list'):
|
||||
continue
|
||||
if name[0:4] == platform_ignore:
|
||||
continue
|
||||
value = self.GetValue(name, radio_dict)
|
||||
no_edit = "choice" in fmt or fmt == 'boolean'
|
||||
txt, cb, btn = self.AddTextComboHelp(self.col, text, value, values, help_text, no_edit)
|
||||
cb.handler = self.OnChange
|
||||
cb.quisk_data_name = name
|
||||
if self.col == 2:
|
||||
self.col = 5
|
||||
else:
|
||||
self.col = 2
|
||||
self.NextRow()
|
||||
self.NextRow()
|
||||
self.AddTextL(1, "End of list", span=7)
|
||||
self.AddColSpacer(3, 20)
|
||||
self.AddColSpacer(6, 20)
|
||||
self.FitInside()
|
||||
self.SetScrollRate(1, 1)
|
||||
def OnChangePassword(self, event):
|
||||
text = local_conf.globals.get('remote_radio_password', '')
|
||||
dlg = wx.TextEntryDialog(self, "Please enter a password for remote access",
|
||||
"Password Entry", text)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
local_conf.globals["remote_radio_password"] = dlg.GetValue()
|
||||
local_conf.settings_changed = True
|
||||
local_conf.SaveState()
|
||||
|
||||
|
||||
class xxRadioFilters(BaseWindow): # The Filters page in the second-level notebook for each radio
|
||||
def __init__(self, parent, radio_name):
|
||||
BaseWindow.__init__(self, parent)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# All QUISK software is Copyright (C) 2006-2024 by James C. Ahlstrom.
|
||||
# All QUISK software is Copyright (C) 2006-2025 by James C. Ahlstrom.
|
||||
# This free software is licensed for use under the GNU General Public
|
||||
# License (GPL), see http://www.opensource.org.
|
||||
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
||||
@@ -585,11 +585,18 @@ class HamlibHandlerSerial:
|
||||
mode = self.app.mode
|
||||
info = cmd
|
||||
info += "%011d" % (self.app.rxFreq + self.app.VFO) # frequency, ZZFA
|
||||
info += '0000'
|
||||
if ritFreq < 0: # RIT freq
|
||||
info += "-%05d" % -ritFreq
|
||||
else:
|
||||
info += "+%05d" % ritFreq
|
||||
if len(cmd) == 4: # Flex ZZIF
|
||||
info += '0000'
|
||||
if ritFreq < 0: # RIT freq
|
||||
info += "-%05d" % -ritFreq
|
||||
else:
|
||||
info += "+%05d" % ritFreq
|
||||
else: # Kenwood IF
|
||||
info += '00000'
|
||||
if ritFreq < 0: # RIT freq
|
||||
info += "-%04d" % -ritFreq
|
||||
else:
|
||||
info += "+%04d" % ritFreq
|
||||
info += "%d" % rit # RIT status
|
||||
info += '0000'
|
||||
if QS.is_key_down(): # MOX, key down
|
||||
@@ -933,6 +940,8 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
if not isinstance(text, Q3StringTypes):
|
||||
text = text.decode('utf-8')
|
||||
self.received += text
|
||||
if HAMLIB_DEBUG and text:
|
||||
print ("Raw rig2 received:", text)
|
||||
if '\n' in self.received: # A complete command ending with newline is available
|
||||
self.input, self.received = self.received.split('\n', 1) # Split off the command, save any further characters
|
||||
else:
|
||||
@@ -1218,6 +1227,267 @@ class HamlibHandlerRig2: # Test with telnet localhost 4532
|
||||
else:
|
||||
self.ErrParam()
|
||||
|
||||
class CatControl:
|
||||
def GetFreqVfo(self, vfoB=False):
|
||||
if vfoB: # The Tx Frequency
|
||||
return self.app.txFreq + self.app.VFO
|
||||
else: # The Rx Frequency
|
||||
rx = self.app.multi_rx_screen.receiver_list
|
||||
if rx:
|
||||
rx = rx[0]
|
||||
return rx.txFreq + rx.VFO
|
||||
else:
|
||||
return self.app.rxFreq + self.app.VFO
|
||||
def SetFreqVfo(self, freq, vfoB=False):
|
||||
if vfoB: # The Tx Frequency
|
||||
self.app.ChangeRxTxFrequency(None, freq)
|
||||
else: # The Rx Frequency
|
||||
# For multiple receivers this controls the first added receiver frequency; else it controls split Rx/Tx frequency.
|
||||
rx = self.app.multi_rx_screen.receiver_list
|
||||
if rx:
|
||||
rx[0].ChangeRxTxFrequency(freq)
|
||||
else:
|
||||
self.app.ChangeRxTxFrequency(freq, None)
|
||||
def GetFilterBW(self):
|
||||
return self.app.filter_bandwidth
|
||||
def SetFilterBW(self, bw):
|
||||
# Choose button closest to requested bandwidth
|
||||
buttons = self.app.filterButns.GetButtons()
|
||||
Lab = buttons[0].GetLabel()
|
||||
diff = abs(int(Lab) - bw)
|
||||
for i in range(1, len(buttons) - 1):
|
||||
label = buttons[i].GetLabel()
|
||||
df = abs(int(label) - bw)
|
||||
if df < diff:
|
||||
Lab = label
|
||||
diff = df
|
||||
self.app.filterButns.SetLabel(Lab, True)
|
||||
|
||||
class ElecraftK4Handler(CatControl): # Test with telnet localhost 9200
|
||||
Mo2CoElecraft = {'LSB':1, 'USB':2, 'CWU':3, 'FM':4, 'AM':5, 'DGT-U':6, 'CWL':7, 'DGT-L':9, 'DGT-FM':4, 'DGT-IQ':6}
|
||||
Co2MoElecraft = {1:'LSB', 2:'USB', 3:'CWU', 4:'FM', 5:'AM', 6:'DGT-U', 7:'CWL', 9:'DGT-L'}
|
||||
def __init__(self, app, conf, sock, address):
|
||||
self.app = app
|
||||
self.conf = conf
|
||||
self.sock = sock
|
||||
sock.settimeout(0.0)
|
||||
self.address = address
|
||||
self.params = '' # params is the string following the command
|
||||
self.received = ''
|
||||
self.cmds = []
|
||||
self.K31Elecraft = False # K31 mode
|
||||
h = self.Handlers = {} # Branch on the first two letters of the command
|
||||
h['AI'] = self.AiAutoInfo
|
||||
h['CW'] = self.CwCWPitch
|
||||
h['DT'] = self.DtDataMode
|
||||
h['FA'] = self.FaFbVfoFreq
|
||||
h['FB'] = self.FaFbVfoFreq
|
||||
h['FT'] = self.FtTxVfo
|
||||
h['FW'] = self.FwFilterBW
|
||||
h['ID'] = self.IdIdent
|
||||
h['IF'] = self.IfInfo
|
||||
h['IS'] = self.IsCenterPitch
|
||||
h['KS'] = self.KsKeyerSpeed
|
||||
h['K3'] = self.K3Elecraft
|
||||
h['LN'] = self.LnLinkVfo
|
||||
h['MD'] = self.MdMode
|
||||
h['OM'] = self.OmOptionModules
|
||||
h['RV'] = self.RvFirmware
|
||||
h['RX'] = self.RxReceive
|
||||
h['SB'] = self.SbSubReceiver
|
||||
h['SM'] = self.SmSmeter
|
||||
h['TX'] = self.TxTransmit
|
||||
def Send(self, text):
|
||||
"""Send text back to the client."""
|
||||
if HAMLIB_DEBUG:
|
||||
for cmd in self.cmds[0:-1]:
|
||||
print ("CatTcp received: %-10s" % cmd)
|
||||
print ("CatTcp received: %-10s ; sent %s" % (self.cmds[-1], text))
|
||||
del self.cmds[:]
|
||||
text = text.encode('utf-8', errors='ignore')
|
||||
try:
|
||||
self.sock.sendall(text)
|
||||
except socket.error:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
def Process(self):
|
||||
"""This is the main processing loop, and is called frequently. It reads and satisfies requests."""
|
||||
if not self.sock:
|
||||
return 0
|
||||
try: # Read any data from the socket
|
||||
text = self.sock.recv(1024)
|
||||
except socket.timeout: # This does not work
|
||||
pass
|
||||
except socket.error: # Nothing to read
|
||||
pass
|
||||
else: # We got some characters
|
||||
text = text.decode('utf-8', errors='ignore')
|
||||
self.received += text
|
||||
if HAMLIB_DEBUG and text:
|
||||
pass #print ("Raw CatTcp received:", text)
|
||||
if ';' in self.received: # A complete command ending with semicolon is available
|
||||
cmd, self.received = self.received.split(';', 1) # Split off the command, save any further characters
|
||||
else:
|
||||
return 1
|
||||
cmd = cmd.strip() # Here is our command line
|
||||
if len(cmd) < 2:
|
||||
return 1
|
||||
if HAMLIB_DEBUG:
|
||||
self.cmds.append(cmd)
|
||||
base = cmd[0:2].upper()
|
||||
try:
|
||||
handler = self.Handlers[base]
|
||||
except KeyError:
|
||||
self.Send(cmd[0:2] + "?;")
|
||||
#print ("Unknown command", cmd)
|
||||
return 1
|
||||
if len(cmd) == 2:
|
||||
args = ''
|
||||
elif cmd[2] == '$': # base is the first two letters upper case and any following "$"
|
||||
base += '$'
|
||||
args = cmd[3:]
|
||||
else:
|
||||
args = cmd[2:]
|
||||
handler(base, args) # args is the remainder
|
||||
return 1
|
||||
def AiAutoInfo(self, base, args):
|
||||
if not args:
|
||||
self.Send("AI0;")
|
||||
elif args != '0':
|
||||
self.Send("AI0;")
|
||||
def CwCWPitch(self, base, args):
|
||||
pitch = self.conf.cwTone
|
||||
pitch = (pitch + 5) // 10
|
||||
if pitch < 25:
|
||||
pitch = 25
|
||||
elif pitch > 95:
|
||||
pitch = 95
|
||||
self.Send("CW%d;" % pitch)
|
||||
def DtDataMode(self, base, args):
|
||||
self.Send("%s0;" % base)
|
||||
def IdIdent(self, base, args):
|
||||
if args:
|
||||
self.Send("ID?;")
|
||||
else:
|
||||
self.Send("ID017;")
|
||||
#self.Send("IDQuisk;")
|
||||
def IfInfo(self, base, args):
|
||||
rxfreq = self.GetFreqVfo()
|
||||
info = "%011d " % rxfreq # Rx frequency, five blanks
|
||||
ritFreq = self.app.ritScale.GetValue()
|
||||
if ritFreq < 0: # +/- RIT freq
|
||||
info += "-%04d" % -ritFreq
|
||||
else:
|
||||
info += "+%04d" % ritFreq
|
||||
if self.app.ritButton.GetValue(): # RIT status, XIT status, blank "00"
|
||||
info += "10 00"
|
||||
else:
|
||||
info += "00 00"
|
||||
if QS.is_key_down(): # MOX, key down
|
||||
info += '1'
|
||||
else:
|
||||
info += '0'
|
||||
mode = self.Mo2CoElecraft.get(self.app.mode, 2)
|
||||
info += "%d" % mode # Mode 1-9
|
||||
info += '00' # Rx on VFOA, no scan
|
||||
if self.app.split_rxtx: # VFO split status
|
||||
info += '1'
|
||||
else:
|
||||
info += '0'
|
||||
info += '001 ;'
|
||||
self.Send(info)
|
||||
def FaFbVfoFreq(self, base, args):
|
||||
if base[1] in ('B', 'b'):
|
||||
vfoB = True
|
||||
else:
|
||||
vfoB = False
|
||||
if not args: # Get command
|
||||
freq = "%06d" % self.GetFreqVfo(vfoB)
|
||||
self.Send(base + freq + ';')
|
||||
else: # Put command
|
||||
try:
|
||||
freq = int(args)
|
||||
except:
|
||||
self.Send(base + "?;")
|
||||
else:
|
||||
l = len(args)
|
||||
if l <= 2:
|
||||
freq *= 1000000
|
||||
elif l <= 5:
|
||||
freq *= 1000
|
||||
self.SetFreqVfo(freq, vfoB)
|
||||
def FtTxVfo(self, base, args):
|
||||
if not args:
|
||||
if self.app.split_rxtx:
|
||||
self.Send("FT1;")
|
||||
else:
|
||||
self.Send("FT0;")
|
||||
else:
|
||||
# Set split Rx/Tx mode.
|
||||
is_split = self.app.splitButton.GetValue()
|
||||
split = int(args)
|
||||
if split and not is_split:
|
||||
self.app.splitButton.SetValue(split, True)
|
||||
elif is_split and not split:
|
||||
self.app.splitButton.SetValue(split, True)
|
||||
def FwFilterBW(self, base, args):
|
||||
if not args:
|
||||
code = self.GetFilterBW()
|
||||
self.Send("%s%04d;" % (base, (code + 5) // 10))
|
||||
else:
|
||||
self.SetFilterBW(int(args) * 10)
|
||||
def IsCenterPitch(self, base, args):
|
||||
if not args:
|
||||
code = self.GetFilterBW() // 2
|
||||
if self.K31Elecraft:
|
||||
self.Send("%s %04d;" % (base, code))
|
||||
else:
|
||||
code = (code + 5) // 10
|
||||
self.Send("%s%04d;" % (base, code))
|
||||
else:
|
||||
if self.K31Elecraft:
|
||||
self.SetFilterBW(int(args) * 2)
|
||||
else:
|
||||
self.SetFilterBW(int(args) * 2 * 10)
|
||||
def K3Elecraft(self, base, args):
|
||||
if args == '1':
|
||||
self.K31Elecraft = True
|
||||
else:
|
||||
self.K31Elecraft = False
|
||||
def KsKeyerSpeed(self, base, args):
|
||||
if len(base) == 2:
|
||||
self.Send("KS013;")
|
||||
def LnLinkVfo(self, base, args):
|
||||
if args != '0':
|
||||
self.Send("LN0;")
|
||||
def MdMode(self, base, args):
|
||||
if args: # set the mode
|
||||
code = int(args)
|
||||
mode = self.Co2MoElecraft[code]
|
||||
self.app.modeButns.SetLabel(mode, True)
|
||||
else: # return the mode
|
||||
code = self.Mo2CoElecraft.get(self.app.mode, 2)
|
||||
self.Send("%s%d;" % (base, code))
|
||||
def OmOptionModules(self, base, args):
|
||||
self.Send("OM ------------;")
|
||||
def RvFirmware(self, base, args):
|
||||
self.Send("%s99.99;" % base)
|
||||
def RxReceive(self, base, args):
|
||||
self.app.pttButton.SetValue(0, True)
|
||||
def SbSubReceiver(self, base, args):
|
||||
self.Send("SB0;")
|
||||
def SmSmeter(self, base, args):
|
||||
if base[0:3].upper() == "SMH":
|
||||
self.Send(base + "?;")
|
||||
elif args:
|
||||
self.Send(base + "?;")
|
||||
elif self.K31Elecraft:
|
||||
self.Send("SM0000;")
|
||||
else:
|
||||
self.Send("SM00;")
|
||||
def TxTransmit(self, base, args):
|
||||
self.app.pttButton.SetValue(1, True)
|
||||
|
||||
class SoundThread(threading.Thread):
|
||||
"""Create a second (non-GUI) thread to read, process and play sound."""
|
||||
def __init__(self, samples_from_python):
|
||||
@@ -3698,6 +3968,19 @@ class App(wx.App):
|
||||
# traceback.print_exc()
|
||||
else:
|
||||
self.hamlib_socket = None
|
||||
# Quisk control by K4 commands through a TCP port
|
||||
self.k4_tcp_clients = [] # list of TCP connections to handle
|
||||
if conf.k4_tcp_port:
|
||||
try:
|
||||
self.k4_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.k4_tcp_socket.bind((conf.k4_tcp_ip, conf.k4_tcp_port))
|
||||
self.k4_tcp_socket.settimeout(0.0)
|
||||
self.k4_tcp_socket.listen(5) # listen for TCP connections from multiple clients
|
||||
except:
|
||||
self.k4_tcp_socket = None
|
||||
# traceback.print_exc()
|
||||
else:
|
||||
self.k4_tcp_socket = None
|
||||
# Quisk control by fldigi
|
||||
self.fldigi_new_freq = None
|
||||
self.fldigi_freq = None
|
||||
@@ -4406,6 +4689,9 @@ class App(wx.App):
|
||||
if self.hamlib_socket:
|
||||
self.hamlib_socket.close()
|
||||
self.hamlib_socket = None
|
||||
if self.k4_tcp_socket:
|
||||
self.k4_tcp_socket.close()
|
||||
self.k4_tcp_socket = None
|
||||
if self.dxCluster:
|
||||
self.dxCluster.stop()
|
||||
if self.hamlib_com1_handler:
|
||||
@@ -6383,7 +6669,7 @@ class App(wx.App):
|
||||
if rxtx != 'rx':
|
||||
self.fldigi_server.main.rx()
|
||||
self.fldigi_timer = time.time()
|
||||
def HamlibPoll(self): # Poll for Hamlib commands
|
||||
def HamlibPoll(self): # Poll for Hamlib and K4 TCP commands
|
||||
if self.hamlib_socket:
|
||||
try: # Poll for new client connections.
|
||||
conn, address = self.hamlib_socket.accept()
|
||||
@@ -6397,6 +6683,19 @@ class App(wx.App):
|
||||
self.hamlib_clients.remove(client)
|
||||
# print ('Remove', client.address)
|
||||
break
|
||||
if self.k4_tcp_socket:
|
||||
try: # Poll for new client connections.
|
||||
conn, address = self.k4_tcp_socket.accept()
|
||||
except socket.error:
|
||||
pass
|
||||
else:
|
||||
# print ('Connection from', address)
|
||||
self.k4_tcp_clients.append(ElecraftK4Handler(self, conf, conn, address))
|
||||
for client in self.k4_tcp_clients: # Service existing clients
|
||||
if not client.Process(): # False return indicates a closed connection; remove the handler for this client
|
||||
self.k4_tcp_clients.remove(client)
|
||||
# print ('Remove', client.address)
|
||||
break
|
||||
def OnKeyHook(self, event):
|
||||
event.Skip()
|
||||
if conf.hot_key_ptt_if_hidden: # Hot key PTT operates even if Quisk is hidden
|
||||
|
||||
+55
-41
@@ -1094,6 +1094,11 @@ file_play_level = 1.000
|
||||
#file_play_level = 0.500
|
||||
#file_play_level = 0.100
|
||||
|
||||
## IQ_Server_IP Pulse server IP address, text
|
||||
# IP Address for remote PulseAudio IQ server.
|
||||
IQ_Server_IP = ""
|
||||
#IQ_Server_IP = "192.168.230.102"
|
||||
|
||||
# Sound card names:
|
||||
#
|
||||
# In PortAudio, soundcards have an index number 0, 1, 2, ... and a name.
|
||||
@@ -1303,51 +1308,15 @@ fixed_tune_offset = 0
|
||||
|
||||
|
||||
################ Remote
|
||||
# DX cluster telent login data, thanks to DJ4CM. Must have station_display_lines > 0.
|
||||
## dxClHost Dx cluster host name, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClHost is the telnet host name.
|
||||
dxClHost = ''
|
||||
#dxClHost = 'example.host.net'
|
||||
|
||||
## dxClPort Dx cluster port number, integer
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClPort is the telnet port number.
|
||||
dxClPort = 7373
|
||||
|
||||
## user_call_sign Call sign for Dx cluster, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# user_call_sign is your call sign which may be needed for login.
|
||||
user_call_sign = ''
|
||||
|
||||
## dxClPassword Password for Dx cluster, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClPassword is the telnet password for the server.
|
||||
dxClPassword = ''
|
||||
#dxClPassword = 'getsomedx'
|
||||
|
||||
## dxClExpireTime Dx cluster expire minutes, integer
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClExpireTime is the time in minutes until DX Cluster entries are removed.
|
||||
dxClExpireTime = 20
|
||||
|
||||
## IQ_Server_IP Pulse server IP address, text
|
||||
#IP Address for remote PulseAudio IQ server.
|
||||
IQ_Server_IP = ""
|
||||
|
||||
## hamlib_ip IP address for Hamlib Rig 2, text
|
||||
# You can control Quisk from Hamlib. Set the Hamlib rig to 2 and the device for rig 2 to
|
||||
# localhost:4575. Or choose a different name and port here. Set the same name and port
|
||||
# in the controlling program.
|
||||
# hamlib_ip is the IP name or address.
|
||||
hamlib_ip = "localhost"
|
||||
hamlib_ip = ""
|
||||
#hamlib_ip = "localhost"
|
||||
|
||||
## hamlib_port IP port for Hamlib, integer
|
||||
## hamlib_port IP port for Hamlib Rig 2, integer
|
||||
# You can control Quisk from Hamlib. For direct control, set the external program to rig 2
|
||||
# "Hamlib NET rigctl", and set the Quisk hamlib port to 4532. To use the rigctld program to control
|
||||
# Quisk, set the Quisk hamlib port to 4575. To turn off Hamlib control, set the Quisk port to zero.
|
||||
@@ -1398,19 +1367,64 @@ win_hamlib_com2_name = ""
|
||||
#win_hamlib_com2_name = "COM15"
|
||||
#win_hamlib_com2_name = "COM16"
|
||||
|
||||
## remote_radio_ip Remote radio IP or name, text
|
||||
## remote_radio_ip IP or name of the remote radio, text
|
||||
# Quisk can be used as a control head to control a real radio located remotely.
|
||||
# This is the IP adddress or the host name of the remote radio.
|
||||
remote_radio_ip = ""
|
||||
#remote_radio_ip = "192.168.1.56"
|
||||
|
||||
## remote_radio_password Password for remote radio, text
|
||||
## remote_radio_password Password for the remote radio, text
|
||||
# Quisk can be used as a control head to control a real radio located remotely.
|
||||
# For security it requires the same password to be entered on both computers.
|
||||
# Enter a fairly long pass phrase of 20 or more characters.
|
||||
# It is only necessary to enter it once on each computer.
|
||||
remote_radio_password = ""
|
||||
|
||||
## k4_tcp_ip IP address for K4 TCP, text
|
||||
# This is the Quisk IP address for the TCP server implementing K4 commands.
|
||||
k4_tcp_ip = ""
|
||||
#k4_tcp_ip = "localhost"
|
||||
#k4_tcp_ip = "127.0.0.1"
|
||||
|
||||
## k4_tcp_port Port number for K4 TCP, integer
|
||||
# This is the Quisk port for the TCP server implementing K4 commands.
|
||||
k4_tcp_port = 0
|
||||
#k4_tcp_port = 9200
|
||||
#k4_tcp_port = 50004
|
||||
|
||||
# DX cluster telent login data, thanks to DJ4CM. Must have station_display_lines > 0.
|
||||
## dxClHost Dx cluster host name, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClHost is the telnet host name.
|
||||
dxClHost = ''
|
||||
#dxClHost = 'example.host.net'
|
||||
|
||||
## dxClPort Dx cluster port number, integer
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClPort is the telnet port number.
|
||||
dxClPort = 7373
|
||||
|
||||
## user_call_sign Call sign for Dx cluster, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# user_call_sign is your call sign which may be needed for login.
|
||||
user_call_sign = ''
|
||||
|
||||
## dxClPassword Password for Dx cluster, text
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClPassword is the telnet password for the server.
|
||||
dxClPassword = ''
|
||||
#dxClPassword = 'getsomedx'
|
||||
|
||||
## dxClExpireTime Dx cluster expire minutes, integer
|
||||
# The Dx cluster options log into a Dx cluster server, and put station information
|
||||
# on the station window under the graph and waterfall screens.
|
||||
# dxClExpireTime is the time in minutes until DX Cluster entries are removed.
|
||||
dxClExpireTime = 20
|
||||
|
||||
|
||||
|
||||
hamlib_com1_name = ""
|
||||
|
||||
@@ -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.41'
|
||||
Version = '4.2.42'
|
||||
|
||||
fp = open("__init__.py", "w") # write title string
|
||||
fp.write("#Quisk version %s\n" % Version)
|
||||
|
||||
Reference in New Issue
Block a user