Skip to content

Commit

Permalink
Quisk version 4.2.37
Browse files Browse the repository at this point in the history
  • Loading branch information
jimahlstrom committed Aug 21, 2024
1 parent 1c9f812 commit 14a80f0
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 42 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
Quisk Version 4.2.37 August 2024
=================================
Quisk now has a new option "Fixed tune offset" on the Config/radio/Options screen. This is meant for
Softrock radios that need a constant audio frequency to simplify amplitude and phase corrections.
The value zero means no change to Quisk tuning. Otherwise enter the fixed frequency offset from center.
If you are using the obsolete "mouse_tune_method = 1" please delete it and use this new feature.
If you use softrock_tune_vfo.py as your hardware file, you should replace it with softrock/hardware_usb.py.

Quisk now works on Python 3.12. And I fixed a bug in the small screen graph width.

Quisk Version 4.2.36 August 2024
=================================
I added two new color schemes by Michael, DK1MI. The On/Off button color and text color can
Expand Down
2 changes: 1 addition & 1 deletion __init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#Quisk version 4.2.36
#Quisk version 4.2.37
from .quisk import main
42 changes: 22 additions & 20 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def GetHardware(self): # Called third to open the hardware file
path = self.NormPath(path)
if not os.path.isfile(path):
dlg = wx.MessageDialog(None,
"Failure for hardware file %s!" % path,
"Can not find the hardware file %s!" % path,
'Hardware File', wx.OK|wx.ICON_ERROR)
ret = dlg.ShowModal()
dlg.Destroy()
Expand Down Expand Up @@ -399,11 +399,12 @@ def AddPages(self, notebk, width): # Called sixth to add pages Help, Radios, all
notebk.AddPage(self.radio_page, "Radios")
self.radios_page_start = notebk.GetPageCount()
notebk.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanging, notebk)
if Settings[1] in Settings[2]:
page = RadioNotebook(notebk, Settings[1])
notebk.AddPage(page, "*%s*" % Settings[1])
for name in Settings[2]:
page = RadioNotebook(notebk, name)
if name == Settings[1]:
notebk.AddPage(page, "*%s*" % name)
else:
if name != Settings[1]:
page = RadioNotebook(notebk, name)
notebk.AddPage(page, name)
def GuessType(self):
udp = conf.use_rx_udp
Expand Down Expand Up @@ -477,7 +478,7 @@ def GetReceiverData(self, receiver_name):
for rxname, data in self.receiver_data:
if rxname == receiver_name:
return data
return None
return ()
def GetReceiverDatum(self, receiver_name, item_name):
for rxname, data in self.receiver_data:
if rxname == receiver_name:
Expand Down Expand Up @@ -563,6 +564,8 @@ def ParseConfig(self):
# self.format4name with the items that Configuration understands.
# Dicts and lists are Python objects. All other items are text, not Python objects.
#
# This parses the quisk_conf_defaults.py file and user-defined radios in ./*pkg/quisk_hardware.py.
#
# Sections start with 16 #, section name
# self.sections is a list of [section_name, section_data]
# section_data is a list of [data_name, text, fmt, help_text, values]
Expand Down Expand Up @@ -595,7 +598,7 @@ def ParseConfig(self):
except:
traceback.print_exc()
def _ParserConf(self, filename):
re_AeqB = re.compile("^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
re_AeqB = re.compile(r"^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
section = None
data_name = None
multi_line = False
Expand Down Expand Up @@ -2227,7 +2230,8 @@ def OnChange2(self, ctrl, value):
if self.radio_name == Settings[1]: # changed for current radio
if name in ('hot_key_ptt_toggle', 'hot_key_ptt_if_hidden', 'keyupDelay', 'cwTone', 'pulse_audio_verbose_output',
'start_cw_delay', 'start_ssb_delay', 'maximum_tx_secs', 'quisk_serial_cts', 'quisk_serial_dsr',
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable'):
'hot_key_ptt1', 'hot_key_ptt2', 'midi_ptt_toggle', 'TxRxSilenceMsec', 'hermes_lite2_enable',
'fixed_tune_offset'):
setattr(conf, name, x)
application.ImmediateChange(name)
elif name[0:4] in ('lin_', 'win_'):
Expand Down Expand Up @@ -2258,6 +2262,9 @@ def OnChange2(self, ctrl, value):
elif hasattr(application.Hardware, "ImmediateChange"):
setattr(conf, name, x)
application.Hardware.ImmediateChange(name)
elif name[0:6] == 'keyer_':
setattr(conf, name, x)
application.ImmediateChange(name)
def FormatOK(self, value, fmt4): # Check formats integer, number, boolean
ok, v = self.EvalItem(value, fmt4)
return ok
Expand Down Expand Up @@ -2660,6 +2667,7 @@ def __init__(self, parent):
choices = []
for name, data in local_conf.receiver_data:
choices.append(name)
choices.sort()
self.add_type = self.AddComboCtrl(2, '', choices=choices, no_edit=True)
self.add_type.SetSelection(0)
item = self.AddTextL(3, "and name the new radio")
Expand Down Expand Up @@ -2743,11 +2751,9 @@ def NewRadioNames(self): # Correct all choice lists for changed radio names
choices = Settings[2][:] # can rename any available radio
self.rename_old.SetItems(choices)
self.rename_old.SetSelection(0)
if "ConfigFileRadio" in choices:
if "ConfigFileRadio" in choices: # can not delete ConfigFileRadio
choices.remove("ConfigFileRadio")
if Settings[1] in choices:
choices.remove(Settings[1])
self.delete_name.SetItems(choices) # can not delete ConfigFileRadio nor the current radio
self.delete_name.SetItems(choices)
self.delete_name.SetSelection(0)
choices = Settings[2] + ["Ask me"]
if "ConfigFileRadio" not in choices:
Expand Down Expand Up @@ -2892,12 +2898,6 @@ def MakeControls(self):
hlp = "To delete a row, select it and press this button."
self.AddTextButtonHelp(2, "Delete Midi row", "Delete", self.OnMidiDelete, hlp)
self.NextRow()
if self.section == "Timing and CW":
if col != 1:
col = 1
self.NextRow()
self.NextRow()
self.AddTextL(1, 'Midi CW key moved to "Keys"', span=4)
if not Keys:
self.AddColSpacer(2, 20)
self.AddColSpacer(5, 20)
Expand Down Expand Up @@ -3688,8 +3688,10 @@ def MakeControls(self):
self.NextRow()
choices = (("48000", "96000", "192000"), ("0", "1"), ("0", "1"), (" ", "0", "1"))
r = 0
if "SoftRock" in self.radio_dict['hardware_file_type']: # Samples come from sound card
softrock = True
if "SoftRock" in self.radio_dict['hardware_file_type']:
softrock = True # Samples come from sound card
elif hasattr(application.Hardware, "use_softrock"):
softrock = application.Hardware.use_softrock
else:
softrock = False
last_row = 8
Expand Down
32 changes: 24 additions & 8 deletions docs.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ <h2 id="Installation">Installation</h2>
<br>
<p>
If you have Python 3 installed on your computer, just continue to use that version.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
If you want to install a more recent version of Python, uninstall the old version first.
Running multiple versions of Python is possible but can be confusing and is not necessary.
A Quisk installation is needed for each version of Python you have.
Expand All @@ -204,7 +204,7 @@ <h3 id="g0.2.2">Windows Initial Installation</h3>
<p>
Windows does not include Python, so you must first install a recent version of 64-bit Python 3.
If you already have 64-bit Python 3 installed, just keep using it.
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11.</b>
<b>On Windows Quisk requires Python 3.8 or 3.9 or 3.10 or 3.11 or 3.12.</b>
There may be newer versions of Python, but until support is added, use the versions listed above.
Python is available from <a href="http://www.python.org/">http://www.python.org</a>.
There is an option to <b>Add Python to PATH</b>. You MUST select
Expand Down Expand Up @@ -239,13 +239,19 @@ <h3 id="g0.2.2">Windows Initial Installation</h3>
<br><br>
python -m pip install --upgrade quisk
<br><br>
You should then be able to start Quisk with the command "quisk".
You can also start Quisk with "python -m quisk".
To create a Quisk shortcut on your desktop, right-click an empty space and select "New" and "Shortcut".
Use "quisk.exe" as the command and "Quisk" as the name.
The "quisk.exe" program is in <b>your_install_directory</b>\Scripts.
Enter this as the location of the item:
</p>
<br>
<pre>
pythonw -m quisk
</pre>
<br>
<p>
and then name the shortcut, perhaps "Quisk".
If you check the properties you will see that Windows expanded the location and start directory to the full path.
If you are curious, Quisk and its Python source files are installed in "<b>your_install_directory</b>\Lib\site-packages\quisk".
If you change to that directory you can run Quisk with "python quisk.py".
If you change to that directory you can run Quisk with "pythonw quisk.py".
</p>
<br>
<p>
Expand Down Expand Up @@ -824,8 +830,14 @@ <h2 id="SoftRock">SoftRock</h2>
A high quality sound card is advisable. The analog mixer is not perfect, and it will be necessary to adjust the I and Q
signals to equal amplitude and 90 degrees phase difference. Use the Config/Config screen buttons to
bring up an adjustment screen. Adjustments must be made for each band, and separately for transmit and receive.
The adjustment depends on both the VFO frequency and the tuning offset from the VFO. See my paper
The adjustment depends on both the VFO frequency and the tuning offset from the VFO.
Quisk has an option to fix the tuning offset to a constant and to tune with the hardware.
The tuning offset then has a constant correction and it is only necessary to correct for the VFO,
a much less sensitive correction.
See my paper
<a href="http://james.ahlstrom.name/phase_corr.html">http://james.ahlstrom.name/phase_corr.html</a> for more information.
<br>
<br>
A good strategy is to pick a VFO near the band center, and record corrections at an Rx frequency of -15000, -1000, 1000 and 15000 Hertz.
If the corrections are sensitive to VFO, record the corrections for these same Rx frequencies at VFOs equal to
or slightly outside the upper and lower band edges.
Expand All @@ -834,6 +846,10 @@ <h2 id="SoftRock">SoftRock</h2>
by hand if you are a Python expert. Otherwise you can just read the values.
<br>
<br>
If you fix the tuning offset to a constant, you can create a correction for VFO at the band center, or
the center and both edges.
<br>
<br>
To create a Receive correction point for a given VFO and frequency, attach a signal generator to the SoftRock through an attenuator,
and look at the image on the graph screen. If the signal is 3500 Hertz above the VFO, the image is 3500 Hertz below it.
Choose "Measure" to measure the corrections and then "Add Cell".
Expand Down
1 change: 1 addition & 0 deletions multuspkg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#
188 changes: 188 additions & 0 deletions multuspkg/quisk_hardware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# This file is part of the Multus project. See https://www.multus-sdr.com/.
# It controls what items appear on the Hardware configuration screen and
# provides logic for each radio.


################ Receivers Multus CW, A transceiver from the Multus project.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = "multuspkg/quisk_hardware.py"

## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = ""

## rx_max_amplitude_correct Max ampl correct, number
# If you get your I/Q samples from a sound card, you will need to correct the
# amplitude and phase for inaccuracies in the analog hardware. The correction is
# entered using the controls from the "Rx Phase" button on the config screen.
# You must enter a positive number. This controls the range of the control.
#rx_max_amplitude_correct = 0.2

## rx_max_phase_correct Max phase correct, number
# If you get your I/Q samples from a sound card, you will need to correct the
# amplitude and phase for inaccuracies in the analog hardware. The correction is
# entered using the controls from the "Rx Phase" button on the config screen.
# You must enter a positive number. This controls the range of the control in degrees.
#rx_max_phase_correct = 10.0

## tx_level Tx Level, dict
# This is the level of the Tx audio sent to SoftRock hardware after all processing as a percentage
# number from 0 to 100.
# The level should be below 100 to allow headroom for amplitude and phase adjustments.
# Changes are immediate (no need to restart).
#tx_level = {}

## digital_tx_level Digital Tx power %, integer
# Digital modes reduce power by the percentage on the config screen.
# This is the maximum value of the slider.
#digital_tx_level = 100

## keyer_speed Keyer speed WPM, integer
# This sets the keyer speed in words per minute.
#keyer_speed = 8
#keyer_speed = 13
#keyer_speed = 18
#keyer_speed = 20
#keyer_speed = 30
#keyer_speed = 40

## keyer_type Type of keyer, text choice
# Use "Straight" mode for a straight key or a bug. Connect the tip of the key to the transceiver.
# Otherwise, select Iambic-A or Iambic-B mode of operation.
#keyer_type = "Straight"
#keyer_type = "Iambic-A"
#keyer_type = "Iambic-B"

## keyer_space Keyer space, text choice
# This sets the type of spacing between elements or letters.
# "Element" attempts to provide proper spacing between the elements of a character.
# (The keyer always provides proper spacing between elements).
# "Letter" turns on letter spacing. This attempts to provide proper spacing between characters.
#keyer_space = "Element"
#keyer_space = "Letter"

## keyer_weight Keyer weight, integer choice
# A weight of 50% means that the dit time equals the inter-element time, which is standard.
# Reducing the weight increases the inter-element time, increasing the weight reduces it.
#keyer_weight = 50
#keyer_weight = 25
#keyer_weight = 75

## keyer_paddle Keyer paddle, text choice
# "Normal" means the left paddle is DIT. "Reverse" means the left paddle is DAH.
#keyer_paddle = "Normal"
#keyer_paddle = "Reverse"

import configure
import softrock
from softrock.hardware_usb import Hardware as BaseHardware
from softrock.hardware_usb import IN, OUT, UBYTE2, UBYTE4
import _quisk as QS

DEBUG = 0

class Hardware(BaseHardware):
def __init__(self, app, conf):
# Set constant parameters here:
conf.usb_vendor_id = 0x16c0
conf.usb_product_id = 0x05dc
conf.si570_i2c_address = 0x55
conf.si570_direct_control = False
conf.si570_xtal_freq = 114285000
conf.repeater_delay = 0.25
BaseHardware.__init__(self, app, conf)
QS.set_sparams(multus_cw_samples=1)
self.use_softrock = True
if DEBUG:
print ("The name of this radio is", configure.Settings[1])
print ("The hardware file type is", conf.hardware_file_type)
def TransferOut(self, address, message): # message is a bytes array or an integer 0 to 255
if isinstance(message, int):
message = message.to_bytes(1, 'big')
if self.usb_dev:
self.usb_dev.ctrl_transfer(OUT, address, self.si570_i2c_address + 0x700, 0, message)
if DEBUG:
print ("USB send to 0x%X" % address, message)
def open(self):
text = BaseHardware.open(self)
self.InitKeyer()
return text
def ChangeMode(self, mode):
ret = BaseHardware.ChangeMode(self, mode)
if mode in ("CWL", "CWU"):
cw_mode = b'C' # Adding "b" does not affect the value sent
if DEBUG:
print ("Mode is CW")
else:
cw_mode = b'U'
if DEBUG:
print ("Mode is not CW")
self.TransferOut(0x70, cw_mode)
return ret
def PollCwKey(self): # Called frequently by Quisk to check the CW key status
return # Quisk is always in Rx
def InitKeyer(self):
# Initialize the keyer parameters
conf = self.conf
if not hasattr(conf, "keyer_speed"):
conf.keyer_speed = 18
if not hasattr(conf, "keyer_type"):
conf.keyer_type = "Straight"
if not hasattr(conf, "keyer_space"):
conf.keyer_space = "Element"
if not hasattr(conf, "keyer_weight"):
conf.keyer_weight = 50
if not hasattr(conf, "keyer_paddle"):
conf.keyer_paddle = "Normal"
# We need to initialize in case the persistent hardware values differ from the Quisk values.
for name in ("keyer_speed", "keyer_type", "keyer_space", "keyer_weight", "keyer_paddle", "cwTone"):
self.ImmediateChange(name)
def ImmediateChange(self, name):
BaseHardware.ImmediateChange(self, name)
value = getattr(self.conf, name)
if DEBUG:
print ("ImmediateChange", name, value)
if name == "keyer_speed":
self.TransferOut(0x7B, value)
elif name == "keyer_type":
if value == "Straight":
mode = 0
elif value == "Iambic-A":
mode = 1
elif value == "Iambic-B":
mode = 2
else:
mode = 0
self.TransferOut(0x71, mode)
elif name == "keyer_space":
if value == "Element":
spacing = 0
elif value == "Letter":
spacing = 1
else:
spacing = 0
self.TransferOut(0x75, spacing)
elif name == "keyer_weight":
weight = int(value)
self.TransferOut(0x77, weight)
elif name == "keyer_paddle":
if value == "Normal":
paddle = 0
elif value == "Reverse":
paddle = 1
else:
paddle = 0
self.TransferOut(0x73, paddle)
elif name == "cwTone":
if value < 500:
tone_index = 0 # 400 Hz
elif 500 <= value < 700:
tone_index = 1 # 600 Hz
elif 700 <= value < 900:
tone_index = 2 # 800 Hz
else:
tone_index = 3 # 1000 Hz
self.TransferOut(0x7F, tone_index)


Loading

0 comments on commit 14a80f0

Please sign in to comment.