Updated local repository to 4.1.53

This commit is contained in:
Rob French 2020-04-29 21:46:57 -05:00
parent 91aa2689ce
commit 4d5471cc5d
21 changed files with 559 additions and 83 deletions

View File

@ -1,3 +1,15 @@
Quisk Version 4.1.53 March 2020
================================
I changed the Afedri radio module to be compatible with Python3. The graph Zoom feature now is available
on the Bandscope screen. Vladimyr Burdeiny provided a patch to make dx cluster work with Python 3.
I extended the range of the Waterfall color controls Ys and Yz to accommodate hardware with a higher
noise floor such as PlutoSDR. You will have to readjust your waterfall colors.
I added a hardware setting for the LNA gain during transmit for the HL2. I made some changes to the CW
and PTT hardware interface for the HL2. These work great with the HL2, but test the new code if you have
diferent Hermes hardware such as Red Pitaya.
Quisk Version 4.1.52 December 2019
===================================
I added an On/Off button to Quisk. When Quisk starts the button is "On". When you turn it to "Off",
@ -7,8 +19,8 @@ and to re-start your hardware after a power down.
Martin Schaller found and fixed a bug in the Alsa sound system that produced sporadic crashes. Thanks Martin!
Max, G7UOZ, found and fixed a bug in the Python3 SoftRock hardware_net.py code. Thanks Max!
This Quisk includes a more recent FreeDV library in both 32-bit and 64-bit versions. The SoapySDR interface
still requires 64-bit Quisk.
This Quisk includes a more recent FreeDV library in both 32-bit and 64-bit versions. The Windows
SoapySDR interface still requires 64-bit Quisk.
The Config/Status screen now shows the dB level of each open sound device. The maximum level is zero dB.
This is meant as an aid to configuring the sound devices.

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.52
Version: 4.1.53
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
Home-page: http://james.ahlstrom.name/quisk/
Author: James C. Ahlstrom

View File

@ -1 +1 @@
#Quisk version 4.1.52
#Quisk version 4.1.53

1
afedrinet/af_comp.bat.makeit Executable file
View File

@ -0,0 +1 @@
gcc -o afedrinet_io.pyd --shared afedrinet_io.c ../is_key_down.c ../import_quisk_api.c -O3 -I"../" -I"C:\Programs\Python27\include" -L"C:\Programs\Python27\libs" -lws2_32 -lpython27

View File

@ -8,10 +8,15 @@ from __future__ import division
# AFEDRI Class module
# Title: afedri.py
# Author: k3it
# Adopted to work with Quisk
# by 4Z5LV
# Last Changes: Sat Feb 02 2013
# Version: 2.2
# Adapted to work with Python3
# by N2ADR
# Last Changes: January 2020
##################################################
from socket import *
@ -56,10 +61,10 @@ class afedri(object):
if not self.s: return 1
__next_freq = target_freq
__next_freq = struct.pack("<q",__next_freq)
__set_freq_cmd = "\x0a\x00\x20\x00\x00" + __next_freq[:5]
__set_freq_cmd = b"\x0a\x00\x20\x00\x00" + __next_freq[:5]
self.s.send(__set_freq_cmd)
__data = self.s.recv(10)
__freq = __data[5:] + "\0" * 3
__freq = __data[5:] + b"\x00\x00\x00"
__freq = struct.unpack("<q",__freq)[0]
return __freq
@ -68,10 +73,10 @@ class afedri(object):
if not self.s: return 1
#__samp_rate = target_samprate
__samp_rate = struct.pack("<L",target_samprate)
__set_rate_cmd = "\x09\x00\xB8\x00\x00" + __samp_rate[:4]
__set_rate_cmd = b"\x09\x00\xB8\x00\x00" + __samp_rate[:4]
self.s.send(__set_rate_cmd)
__data = self.s.recv(9)
__samp = __data[5:] + "\0" * 4
__samp = __data[5:] + b"\x00\x00\x00\x00"
__samp = struct.unpack("<q",__samp)[0]
return __samp
@ -81,7 +86,7 @@ class afedri(object):
__gain = target_gain
# special afedri calculation for the gain byte
__gain = ((__gain+10)/3 << 3) + 1
__set_gain_cmd = "\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
__set_gain_cmd = b"\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
self.s.send(__set_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:6])[0]>>3)
@ -92,7 +97,7 @@ class afedri(object):
__gain = (indx << 3) + 1
# special afedri calculation for the gain byte
#__gain = ((__gain+10)/3 << 3) + 1
__set_gain_cmd = "\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
__set_gain_cmd = b"\x06\x00\x38\x00\x00" + struct.pack("B",__gain)
self.s.send(__set_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:6])[0]>>3)
@ -103,7 +108,7 @@ class afedri(object):
NOT IMPLEMENTED IN AFEDRI?. DON'T USE
"""
if not self.s: return 1
__get_gain_cmd = "\x05\x20\x38\x00\x00"
__get_gain_cmd = b"\x05\x20\x38\x00\x00"
self.s.send(__get_gain_cmd)
__data = self.s.recv(6)
__rf_gain = -10 + 3 * (struct.unpack("B",__data[5:])[0]>>3)
@ -111,8 +116,8 @@ class afedri(object):
def get_fe_clock(self):
if not self.s: return 1
__get_lword_cmd = "\x09\xE0\x02\x55\x00\x00\x00\x00\x00"
__get_hword_cmd = "\x09\xE0\x02\x55\x01\x00\x00\x00\x00"
__get_lword_cmd = b"\x09\xE0\x02\x55\x00\x00\x00\x00\x00"
__get_hword_cmd = b"\x09\xE0\x02\x55\x01\x00\x00\x00\x00"
self.s.send(__get_lword_cmd)
__data_l = self.s.recv(9)
self.s.send(__get_hword_cmd)
@ -123,7 +128,7 @@ class afedri(object):
def start_capture(self):
#start 16-bit contiguous capture, complex numbers
if not self.s: return 1
__start_cmd="\x08\x00\x18\x00\x80\x02\x00\x00"
__start_cmd=b"\x08\x00\x18\x00\x80\x02\x00\x00"
self.s.send(__start_cmd)
__data = self.s.recv(8)
return __data
@ -131,14 +136,15 @@ class afedri(object):
def get_sdr_name(self):
#Request SDR's Name string command = array.array('B',[0x4, 0x20,1,0])
if not self.s: return 1
__start_cmd="\x04\x20\x01\x00"
__start_cmd=b"\x04\x20\x01\x00"
self.s.send(__start_cmd)
__data = self.s.recv(16)
__data = __data.decode('utf-8')
return __data
def stop_capture(self):
if not self.s: return 1
__stop_cmd="\x08\x00\x18\x00\x00\x01\x00\x00"
__stop_cmd=b"\x08\x00\x18\x00\x00\x01\x00\x00"
self.s.send(__stop_cmd)
__data = self.s.recv(8)
return __data
@ -150,8 +156,8 @@ class afedri(object):
__DISCOVER_SERVER_PORT=48321 # PC client Tx port, SDR Server Rx Port
__DISCOVER_CLIENT_PORT=48322 # PC client Rx port, SDR Server Tx Port
__data="\x38\x00\x5a\xa5" # magic discovery packet
__data=__data.ljust(56,"\x00") # pad with zeroes
__data=b"\x38\x00\x5a\xa5" # magic discovery packet
__data=__data.ljust(56,b"\x00") # pad with zeroes
self.s = socket(AF_INET, SOCK_DGRAM)
self.s.bind(('', 0))
@ -168,7 +174,9 @@ class afedri(object):
try:
__msg=self.sin.recv(256,0)
__devname=__msg[5:20]
__devname=__devname.decode('utf-8')
__sn=__msg[21:36]
__sn=__sn.decode('utf-8')
__ip=inet_ntoa(__msg[40:36:-1])
__port=struct.unpack("<H",__msg[53:55])[0]
self.s.close()

View File

@ -7,14 +7,12 @@ from __future__ import division
import os,sys
os.environ['PATH'] = os.path.dirname(__file__) + ';' + os.environ['PATH']
#import array
#import socket # Import socket module
from afedri import *
#import afedri
import afedrinet
import afedrinet.afedri
class Control:
def __init__(self, sdr_address="192.168.0.8", sdr_port=50000):
self.hw = afedri(sdr_address, sdr_port)
self.hw = afedrinet.afedri.afedri(sdr_address, sdr_port)
if self.hw.s is None: # Failure to find the hardware
self.hw = None
def OpenHW(self):

View File

@ -1106,7 +1106,9 @@ class BaseWindow(wx.ScrolledWindow):
application.Hardware.SetLowPwrEnable(x)
elif name == 'hermes_power_amp':
application.Hardware.EnablePowerAmp(x)
elif name == "hermes_bias_adjust":
elif name == 'hermes_TxLNA_dB':
application.Hardware.ChangeTxLNA(x)
elif name == "hermes_bias_adjust" and self.HermesBias0:
self.HermesBias0.Enable(x)
self.HermesBias1.Enable(x)
self.HermesWriteBiasButton.Enable(x)
@ -1424,6 +1426,8 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
def __init__(self, parent, radio_name):
RadioHardwareBase.__init__(self, parent, radio_name)
self.AlwaysMakeControls()
self.HermesBias0 = None
self.HermesBias1 = None
radio_dict = local_conf.GetRadioDict(radio_name)
radio_type = radio_dict['hardware_file_type']
data_names = local_conf.GetReceiverData(radio_type)
@ -1435,7 +1439,7 @@ class RadioHardware(RadioHardwareBase): # The Hardware page in the second-level
hermes_board_id = application.Hardware.hermes_board_id
except:
pass
if hasattr(application.Hardware, "ProgramGateware"):
if radio_name == Settings[1] and hasattr(application.Hardware, "ProgramGateware"):
help_text = "Choose an RBF file and program the Gateware (FPGA software) over Ethernet."
self.AddTextButtonHelp(1, "Gateware Update", "Program from RBF file..", application.Hardware.ProgramGateware, help_text)
col = 1

View File

@ -257,8 +257,12 @@ sudo apt-get install portaudio19-dev
<br>
sudo apt-get install libpulse-dev
<br>
sudo apt-get install python-dev
<br>
sudo apt-get install libpython2.7-dev
<br>
sudo apt-get install python3-dev
<br>
sudo apt-get install libpython3-dev
<br>
sudo apt-get install python-usb
@ -838,14 +842,6 @@ just
changing the sound card names.
<br>
<br>
For more information try these articles:<span style="text-decoration: underline;">
<br>
</span><a href="http://linuxplanet.com/linuxplanet/tutorials/6465/1/">http://linuxplanet.com/linuxplanet/tutorials/6465/1/</a>
<br>
<a href="http://linuxplanet.com/linuxplanet/tutorials/6466/1/">http://linuxplanet.com/linuxplanet/tutorials/6466/1/</a>
<br>
<br>
<h3 id="g0.4.4">Windows Names</h3>
To see what sound cards you have, use the Control Panel item Sound

View File

@ -110,17 +110,17 @@ class DxCluster(threading.Thread):
for i in range(10):
try:
self.tn.open(conf.dxClHost, conf.dxClPort, 10)
self.tn.read_until('login:', 10)
self.tn.write(str(conf.user_call_sign) + "\n") # user_call_sign may be Unicode
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:
time.sleep(0.5)
if conf.dxClPassword:
self.tn.read_until("Password: ")
self.tn.write(str(conf.dxClPassword) + "\n")
self.tn.read_until(b"Password: ")
self.tn.write(conf.dxClPassword.encode('utf-8', errors='ignore') + b"\n")
def telnetRead(self):
message = self.tn.read_until('\n', 60).decode(encoding='utf-8', errors='replace')
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):

View File

@ -71,6 +71,7 @@ class Hardware(BaseHardware):
self.SetControlByte(0x10, 1, (value >> 2) & 0xFF) # cw_hang_time
self.SetLowPwrEnable(conf.hermes_lowpwr_tr_enable)
self.EnablePowerAmp(conf.hermes_power_amp)
self.ChangeTxLNA(conf.hermes_TxLNA_dB)
self.MakePowerCalibration()
def pre_open(self):
# This socket is used for the Metis Discover protocol
@ -395,8 +396,10 @@ class Hardware(BaseHardware):
btn = event.GetEventObject()
if btn.GetValue():
QS.set_PTT(1)
QS.set_key_down(1)
else:
QS.set_PTT(0)
QS.set_key_down(0)
def OnSpot(self, level):
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
pass
@ -437,9 +440,9 @@ class Hardware(BaseHardware):
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change AGC to", value)
## Simpler LNA setting for HL2 identifying as version >=40, see HL2 wiki for details
def ChangeLNA(self, value):
def ChangeLNA(self, value): # LNA for Rx
# value is -12 to +48
if self.hermes_code_version < 40:
if self.hermes_code_version < 40: # Is this correct ??
if value < 20:
self.pc2hermes[2] |= 0x08 # C0 index == 0, C3[3]: LNA +32 dB disable == 1
value = 19 - value
@ -451,6 +454,12 @@ class Hardware(BaseHardware):
self.pc2hermes[4 * 10 + 3] = value # C0 index == 0x1010, C4[4:0] LNA 0-32 dB gain
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change LNA to", value)
def ChangeTxLNA(self, value): # LNA for Tx
# value is -12 to +48
value = ((value+12) & 0x3f) | 0x40 | 0x80
self.SetControlByte(0x0e, 3, value) # C0 index == 0x0e, C3
QS.pc_to_hermes(self.pc2hermes)
if DEBUG: print ("Change Tx LNA to", value)
def SetTxLevel(self):
try:
tx_level = self.conf.tx_level[self.band]

42
quisk.c
View File

@ -3347,17 +3347,15 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
unsigned int seq;
unsigned int hlwp = 0;
static unsigned int seq0;
static int key_state;
static int tx_records;
static int max_multirx_count=0;
int i, j, nSamples, xr, xi, index, start, want_samples, dindex, state, num_records;
int i, j, nSamples, xr, xi, index, start, want_samples, dindex, num_records;
complex double c;
struct timeval tm_wait;
fd_set fds;
if ( ! quisk_hermes_is_ready(rx_udp_socket)) {
seq0 = 0;
key_state = 0;
tx_records = 0;
quisk_rx_udp_started = 0;
multirx_fft_next_index = 0;
@ -3501,23 +3499,8 @@ static int read_rx_udp10(complex double * samp) // Read samples from UDP using t
//code_version = quisk_hermes_to_pc[3];
if ((quisk_hermes_to_pc[0] & 0x01) != 0) // C1
quisk_sound_state.overrange++;
if (quisk_hermes_code_version >= 62) {
hardware_ptt = buf[start] & 0x01; // C0 bit zero is PTT
hardware_cwkey = (buf[start] & 0x02) >> 1; // C0 bit one is CW key state
}
else {
hardware_cwkey = buf[start] & 0x01; // C0 bit zero is CW key state
}
if (rxMode == CWL || rxMode == CWU) {
state = hardware_cwkey | is_PTT_down;
}
else {
state = is_PTT_down;
}
if (state != key_state) {
key_state = state;
quisk_set_key_down(state);
}
hardware_ptt = buf[start] & 0x01; // C0 bit zero is PTT
hardware_cwkey = (buf[start] & 0x04) >> 2; // C0 bit two is CW key state
}
else if(dindex == 1) { // temperature and forward power
hermes_temperature += quisk_hermes_to_pc[4] << 8 | quisk_hermes_to_pc[5];
@ -4548,15 +4531,19 @@ static PyObject * get_multirx_graph(PyObject * self, PyObject * args) // Called
return retrn;
}
static PyObject * get_bandscope(void) // Called by the GUI thread
static PyObject * get_bandscope(PyObject * self, PyObject * args) // Called by the GUI thread
{
int i, j, j1, j2, L;
int i, j, j1, j2, L, clock;
double zoom, deltaf, rate, f1;
static int fft_count = 0;
static double the_max = 0;
static double time0=0; // time of last graph
double d1, d2, sample, frac, scale;
PyObject * tuple2;
if (!PyArg_ParseTuple (args, "idd", &clock, &zoom, &deltaf))
return NULL;
if (bandscopeState == 99 && bandscopePlan) { // bandscope samples are ready
for (i = 0; i < bandscope_size; i++) {
d1 = fabs(bandscopeSamples[i]);
@ -4577,9 +4564,12 @@ static PyObject * get_bandscope(void) // Called by the GUI thread
tuple2 = PyTuple_New(graph_width);
frac = (double)L / graph_width;
scale = 1.0 / frac / fft_count / bandscope_size;
rate = clock / 2.0;
for (i = 0; i < graph_width; i++) { // for each pixel
d1 = i * frac;
d2 = (i + 1) * frac;
f1 = deltaf + rate / 2.0 * (1.0 - zoom); // frequency at left of graph
// freq = f1 + pixel / graph_width + zoom * rate = rate * fft_index / L
d1 = L / rate * (f1 + (double)i / graph_width * zoom * rate);
d2 = L / rate * (f1 + (double)(i + 1) / graph_width * zoom * rate);
j1 = floor(d1);
j2 = floor(d2);
if (j1 == j2) {
@ -4627,9 +4617,6 @@ static PyObject * get_graph(PyObject * self, PyObject * args) // Called by the G
if (!PyArg_ParseTuple (args, "idd", &k, &zoom, &deltaf))
return NULL;
if (k == 2) {
return get_bandscope();
}
if (k != use_fft) { // change in data return type; re-initialize
use_fft = k;
count_fft = 0;
@ -5183,6 +5170,7 @@ static PyMethodDef QuiskMethods[] = {
{"is_key_down", is_key_down, METH_VARARGS, "Check whether the key is down; return 0 or 1."},
{"get_state", get_state, METH_VARARGS, "Return a count of read and write errors."},
{"get_graph", get_graph, METH_VARARGS, "Return a tuple of graph data."},
{"get_bandscope", get_bandscope, METH_VARARGS, "Return a tuple of bandscope data."},
{"set_multirx_mode", set_multirx_mode, METH_VARARGS, "Select demodulation mode for sub-receivers."},
{"set_multirx_freq", set_multirx_freq, METH_VARARGS, "Select how to play audio from sub-receivers."},
{"set_multirx_play_method", set_multirx_play_method, METH_VARARGS, "Select how to play audio from sub-receivers."},

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.52
Version: 4.1.53
Summary: QUISK is a Software Defined Radio (SDR) transceiver that can control various radio hardware.
Home-page: http://james.ahlstrom.name/quisk/
Author: James C. Ahlstrom

View File

@ -41,6 +41,7 @@ quisk.py
quisk_conf_defaults.py
quisk_conf_kx3.py
quisk_conf_model.py
quisk_conf_openradio.py
quisk_conf_peaberry.py
quisk_conf_sdr8600.py
quisk_conf_sdriq.py
@ -50,10 +51,12 @@ quisk_hardware_hamlib.py
quisk_hardware_model.py
quisk_hardware_sdr8600.py
quisk_hardware_sdriq.py
quisk_hardware_sdrmicron.py
quisk_utils.py
quisk_vna.py
quisk_widgets.py
setup.py
softrock_tune_vfo.py
sound.c
sound_alsa.c
sound_directx.c
@ -86,6 +89,7 @@ winsound.txt
./quisk_conf_defaults.py
./quisk_conf_kx3.py
./quisk_conf_model.py
./quisk_conf_openradio.py
./quisk_conf_peaberry.py
./quisk_conf_sdr8600.py
./quisk_conf_sdriq.py
@ -95,9 +99,11 @@ winsound.txt
./quisk_hardware_model.py
./quisk_hardware_sdr8600.py
./quisk_hardware_sdriq.py
./quisk_hardware_sdrmicron.py
./quisk_utils.py
./quisk_vna.py
./quisk_widgets.py
./softrock_tune_vfo.py
./windows.txt
./winsound.txt
./afedrinet/SOURCE.txt
@ -162,12 +168,11 @@ winsound.txt
./softrock/widgets_tx.py
afedrinet/SOURCE.txt
afedrinet/__init__.py
afedrinet/af_comp.bat
afedrinet/af_comp.bat.makeit
afedrinet/afe_library
afedrinet/afe_library.mac
afedrinet/afedri.py
afedrinet/afedrinet_io.c
afedrinet/afedrinet_io.pyd
afedrinet/quisk_conf.py
afedrinet/quisk_conf_linux.py
afedrinet/quisk_conf_mac.py

View File

@ -857,6 +857,7 @@ class ConfigScreen(wx.Panel):
def __init__(self, parent, width, fft_size):
self.y_scale = 0
self.y_zero = 0
self.zoom_control = 0
self.finish_pages = True
self.width = width
wx.Panel.__init__(self, parent)
@ -920,6 +921,7 @@ class ConfigStatus(wx.ScrolledWindow):
self.latencyPlay = -1
self.y_scale = 0
self.y_zero = 0
self.zoom_control = 0
self.rate_min = -1
self.rate_max = -1
self.chan_min = -1
@ -1779,6 +1781,7 @@ class GraphScreen(wx.Window):
else:
self.y_scale = conf.graph_y_scale
self.y_zero = conf.graph_y_zero
self.zoom_control = 0
self.y_ticks = []
self.VFO = 0
self.filter_mode = 'AM'
@ -1879,9 +1882,10 @@ class GraphScreen(wx.Window):
def ChangeYzero(self, y_zero):
self.y_zero = y_zero
self.doResize = True
def ChangeZoom(self, zoom, deltaf):
def ChangeZoom(self, zoom, deltaf, zoom_control):
self.zoom = zoom
self.zoom_deltaf = deltaf
self.zoom_control = zoom_control
self.doResize = True
def MakeYScale(self):
chary = self.chary
@ -2491,14 +2495,19 @@ class WaterfallDisplay(wx.Window):
#T('graph start')
row = bytearray(0) # Make a new row of pixels for a one-line image
gain = self.rf_gain
# y_scale and y_zero range from zero to 160.
# y_zero controls the center position of the colors. Set to a bit over the noise level.
# y_scale controls how much the colors change when the sample deviates from y_zero.
for x in data: # x is -130 to 0, or so (dB)
l = int((x - gain + y_zero // 3 + 100) * y_scale / 10)
yz = 40.0 + y_zero * 0.69 # -yz is the color center in dB
l = int((x - gain + yz) * (y_scale + 10) * 0.10 + 128)
l = max(l, 0)
l = min(l, 255)
row.append(self.red[l])
row.append(self.green[l])
row.append(self.blue[l])
row.append(255)
#print ('OnGraphData yz %.0f, slope %.3f, l %4d' % (yz, (y_scale + 10) * 0.10, l))
#T('graph string')
if wxVersion in ('2', '3'):
bmp = wx.BitmapFromBufferRGBA(len(row) // 4, 1, row)
@ -2523,15 +2532,17 @@ class WaterfallDisplay(wx.Window):
dc.DrawLine(tune_tx, self.margin, tune_tx, self.height)
self.tune_tx = tune_tx
self.tune_rx = tune_rx
def ChangeZoom(self, zoom, deltaf):
def ChangeZoom(self, zoom, deltaf, zoom_control):
self.zoom = zoom
self.zoom_deltaf = deltaf
self.zoom_control = zoom_control
class WaterfallScreen(wx.SplitterWindow):
"""Create a splitter window with a graph screen and a waterfall screen"""
def __init__(self, frame, width, data_width, graph_width):
self.y_scale = conf.waterfall_y_scale
self.y_zero = conf.waterfall_y_zero
self.zoom_control = 0
wx.SplitterWindow.__init__(self, frame)
self.SetSizeHints(width, -1, width)
self.SetSashGravity(0.50)
@ -2576,6 +2587,11 @@ class WaterfallScreen(wx.SplitterWindow):
self.pane2.OnGraphData(data)
def ChangeRfGain(self, gain): # Set the correction for RF gain
self.pane2.display.rf_gain = gain
def ChangeZoom(self, zoom, deltaf, zoom_control):
self.zoom_control = zoom_control
self.pane1.ChangeZoom(zoom, deltaf, zoom_control)
self.pane2.ChangeZoom(zoom, deltaf, zoom_control)
self.pane2.display.ChangeZoom(zoom, deltaf, zoom_control)
class WaterfallPane(GraphScreen):
"""Create a waterfall screen with an X axis and a waterfall display."""
@ -2583,6 +2599,7 @@ class WaterfallPane(GraphScreen):
GraphScreen.__init__(self, frame, data_width, graph_width)
self.y_scale = conf.waterfall_y_scale
self.y_zero = conf.waterfall_y_zero
self.zoom_control = 0
self.oldVFO = self.VFO
self.filter_mode = 'AM'
self.filter_bandwidth = 0
@ -3004,6 +3021,7 @@ class ScopeScreen(wx.Window):
self.horizPen = wx.Pen(conf.color_gl, 1, wx.SOLID)
self.y_scale = conf.scope_y_scale
self.y_zero = conf.scope_y_zero
self.zoom_control = 0
self.yscale = 1
self.running = 1
self.doResize = False
@ -3136,8 +3154,11 @@ class ScopeScreen(wx.Window):
class BandscopeScreen(WaterfallScreen):
def __init__(self, frame, width, data_width, graph_width, clock):
self.zoom = 1.0
self.zoom_deltaf = 0
self.zoom_control = 0
WaterfallScreen.__init__(self, frame, width, data_width, graph_width)
self.pane1.sample_rate = self.pane2.sample_rate = int(clock) // 2
self.sample_rate = self.pane1.sample_rate = self.pane2.sample_rate = int(clock) // 2
self.VFO = clock // 4
self.SetVFO(self.VFO)
def SetTxFreq(self, tx_freq, rx_freq):
@ -3146,6 +3167,26 @@ class BandscopeScreen(WaterfallScreen):
def SetFrequency(self, freq): # freq is 7000000, not the offset from VFO
freq = freq - self.VFO
WaterfallScreen.SetTxFreq(self, freq, freq)
def ChangeZoom(self, zoom_control): # zoom_control is the slider value 0 to 1000
self.zoom_control = zoom_control
if zoom_control < 50:
zoom = 1.0
zoom_deltaf = 0
else:
zoom = 1.0 - zoom_control / 1000.0 * 0.95
freq = application.rxFreq + application.VFO
srate = int(self.sample_rate * zoom) # reduced (zoomed) sample rate
if freq - srate // 2 < 0:
zoom_deltaf = srate // 2 - self.VFO
elif freq + srate // 2 > self.sample_rate:
zoom_deltaf = self.VFO - srate // 2
else:
zoom_deltaf = freq - self.VFO
self.zoom = zoom
self.zoom_deltaf = zoom_deltaf
self.pane1.ChangeZoom(zoom, zoom_deltaf, zoom_control)
self.pane2.ChangeZoom(zoom, zoom_deltaf, zoom_control)
self.pane2.display.ChangeZoom(zoom, zoom_deltaf, zoom_control)
class FilterScreen(GraphScreen):
"""Create a graph of the receive filter response."""
@ -3153,6 +3194,7 @@ class FilterScreen(GraphScreen):
GraphScreen.__init__(self, parent, data_width, graph_width)
self.y_scale = conf.filter_y_scale
self.y_zero = conf.filter_y_zero
self.zoom_control = 0
self.VFO = 0
self.txFreq = 0
self.data = []
@ -3199,6 +3241,7 @@ class AudioFFTScreen(GraphScreen):
GraphScreen.__init__(self, parent, data_width, graph_width)
self.y_scale = conf.filter_y_scale
self.y_zero = conf.filter_y_zero
self.zoom_control = 0
self.VFO = 0
self.txFreq = 0
self.sample_rate = sample_rate
@ -3216,6 +3259,7 @@ class HelpScreen(wx.html.HtmlWindow):
wx.html.HtmlWindow.__init__(self, parent, -1, size=(width, height))
self.y_scale = 0
self.y_zero = 0
self.zoom_control = 0
if "gtk2" in wx.PlatformInfo:
self.SetStandardFonts()
self.SetFonts("", "", [10, 12, 14, 16, 18, 20, 22])
@ -4907,6 +4951,7 @@ The new code supports multiple corrections per band.""")
self.vertBox.Layout() # This destroys the initialized sash position!
self.sliderYs.SetValue(self.screen.y_scale)
self.sliderYz.SetValue(self.screen.y_zero)
self.sliderZo.SetValue(self.screen.zoom_control)
if name == 'WFall':
self.screen.SetSashPosition(sash)
def OnBtnFileRecord(self, event):
@ -4929,14 +4974,21 @@ The new code supports multiple corrections per band.""")
elif self.multi_rx_screen.rx_zero == self.graph:
self.graphScaleZ[self.lastBand] = (self.graph.y_scale, self.graph.y_zero)
def OnChangeZoom(self, event):
x = self.sliderZo.GetValue()
if x < 50:
zoom_control = self.sliderZo.GetValue()
if self.screen == self.bandscope_screen:
self.bandscope_screen.ChangeZoom(zoom_control)
self.bandscope_screen.SetTxFreq(self.txFreq, self.rxFreq)
return
# The display runs from f1 to f2. The original sample rate is "rate".
# The new effective sample rate is rate * zoom.
# f1 = deltaf + rate * (1 - zoom) / 2
if zoom_control < 50:
self.zoom = 1.0 # change back to not-zoomed mode
self.zoom_deltaf = 0
self.zooming = False
else:
a = 1000.0 * self.sample_rate / (self.sample_rate - 2500.0)
self.zoom = 1.0 - x / a
self.zoom = 1.0 - zoom_control / a
if not self.zooming: # set deltaf when zoom mode starts
center = self.multi_rx_screen.graph.filter_center
freq = self.rxFreq + center
@ -4944,10 +4996,8 @@ The new code supports multiple corrections per band.""")
self.zooming = True
zoom = self.zoom
deltaf = self.zoom_deltaf
self.graph.ChangeZoom(zoom, deltaf)
self.waterfall.pane1.ChangeZoom(zoom, deltaf)
self.waterfall.pane2.ChangeZoom(zoom, deltaf)
self.waterfall.pane2.display.ChangeZoom(zoom, deltaf)
self.graph.ChangeZoom(zoom, deltaf, zoom_control)
self.waterfall.ChangeZoom(zoom, deltaf, zoom_control)
self.screen.SetTxFreq(self.txFreq, self.rxFreq)
self.station_screen.Refresh()
def OnLevelVOX(self, event):
@ -5851,7 +5901,7 @@ The new code supports multiple corrections per band.""")
self.SetPTT(False)
self.timer = time.time()
if self.bandscope_clock: # Hermes UDP protocol
data = QS.get_graph(2, 1.0, 0)
data = QS.get_bandscope(self.bandscope_clock, self.bandscope_screen.zoom, float(self.bandscope_screen.zoom_deltaf))
if data and self.screen == self.bandscope_screen:
self.screen.OnGraphData(data)
if self.screen == self.scope:

View File

@ -429,6 +429,11 @@ Hware_Hl2_EepromMAC = '0xA1 0x6B'
Hware_Hl2_EepromMACUse = 'Ignore'
#Hware_Hl2_EepromMACUse = 'Set address'
## hermes_TxLNA_dB LNA during Tx dB, integer
# During transmit the low noise Rx amplifier gain changes to this value (in dB) if the hardware supports it.
# Changes are immediate (no need to restart).
hermes_TxLNA_dB = 21
# These are known power meter calibration tables. This table is not present in the JSON settings file.
power_meter_std_calibrations = {}
power_meter_std_calibrations['HL2FilterE3'] = [[ 0, 0.0 ], [ 25.865384615384617, 0.0025502539351328003 ], [ 101.02453987730061, 0.012752044999999998 ],

122
quisk_conf_openradio.py Executable file
View File

@ -0,0 +1,122 @@
# OpenRadio v1.1 Quisk Configuration File
#
# IMPORTANT: To be able to control the OpenRadio board from within Quisk,
# you will need to compile and upload the 'openradio_quisk' firmware, which
# is available from: https://github.com/darksidelemm/open_radio_miniconf_2015
#
# You will also need to install the pyserial package for python.
#
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
# SOUND CARD SETTINGS
#
# Uncomment these if you wish to use PortAudio directly
#name_of_sound_capt = "portaudio:(hw:2,0)"
#name_of_sound_play = "portaudio:(hw:1,0)"
# Uncomment these lines if you wish to use Pulseaudio
name_of_sound_capt = "pulse"
name_of_sound_play = "pulse"
# SERIAL PORT SETTINGS
# Set this as appropriate for your OS.
openradio_serial_port = "/dev/ttyUSB0"
openradio_serial_rate = 57600
# OpenRadio Frequency limits.
# These are just within the limits set in the openradio_quisk firmware.
openradio_lower = 100001
openradio_upper = 29999999
# OpenRadio Hardware Control Class
#
import serial,time
from quisk_hardware_model import Hardware as BaseHardware
class Hardware(BaseHardware):
def open(self):
# Called once to open the Hardware
# Open the serial port.
self.or_serial = serial.Serial(openradio_serial_port,openradio_serial_rate,timeout=3)
print("Opened Serial Port.")
# Wait for the Arduino Nano to restart and boot.
time.sleep(2)
# Poll for version. Should probably confirm the response on this.
version = str(self.get_parameter("VER"))
print(version)
# Return an informative message for the config screen
t = version + ". Capture from sound card %s." % self.conf.name_of_sound_capt
return t
def close(self):
# Called once to close the Hardware
self.or_serial.close()
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
# Called whenever quisk requests a frequency change.
# This sends the FREQ command to set the centre frequency of the OpenRadio,
# and will also move the 'tune' frequency (the section within the RX passband
# which is to be demodulated) if it falls outside the passband (+/- sample_rate/2).
print("Setting VFO to %d." % vfo)
if(vfo<openradio_lower):
vfo = openradio_lower
print("Outside range! Setting to %d" % openradio_lower)
if(vfo>openradio_upper):
vfo = openradio_upper
print("Outside range! Setting to %d" % openradio_upper)
success = self.set_parameter("FREQ",str(vfo))
# If the tune frequency is outside the RX bandwidth, set it to somewhere within that bandwidth.
if(tune>(vfo + sample_rate/2) or tune<(vfo - sample_rate/2)):
tune = vfo + 10000
print("Bringing tune frequency back into the RX bandwidth.")
if success:
print("Frequency change succeeded!")
else:
print("Frequency change failed.")
return tune, vfo
#
# Serial comms functions, to communicate with the OpenRadio board
#
def get_parameter(self,string):
self.or_serial.write(string+"\n")
return self.get_argument()
def set_parameter(self,string,arg):
self.or_serial.write(string+","+arg+"\n")
if self.get_argument() == arg:
return True
else:
return False
def get_argument(self):
data1 = self.or_serial.readline()
# Do a couple of quick checks to see if there is useful data here
if len(data1) == 0:
return -1
# Maybe we didn't catch an OK line?
if data1.startswith('OK'):
data1 = self.or_serial.readline()
# Check to see if we have a comma in the string. If not, there is no argument.
if data1.find(',') == -1:
return -1
data1 = data1.split(',')[1].rstrip('\r\n')
# Check for the OK string
data2 = self.or_serial.readline()
if data2.startswith('OK'):
return data1

259
quisk_hardware_sdrmicron.py Executable file
View File

@ -0,0 +1,259 @@
# -*- coding: cp1251 -*-
#
# It provides support for the SDR Micron
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, wx, traceback
import ftd2xx as d2xx
import time
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 0
# https://github.com/Dfinitski/SDR-Micron
#
# RX control, to device, 32 bytes total
# Preamble + 'RX0' + enable + rate + 4 bytes frequency + attenuation + 14 binary zeroes
#
# where:
# Preamble is 7*0x55, 0xd5
# bytes:
# enable - binary 0 or 1, for enable receiver
# rate:
# binary
# 0 for 48 kHz
# 1 for 96 kHz
# 2 for 192 kHz
# 3 for 240 kHz
# 4 for 384 kHz
# 5 for 480 kHz
# 6 for 640 kHz
# 7 for 768 kHz
# 8 for 960 kHz
# 9 for 1536 kHz
# 10 for 1920 kHz
#
# frequency - 32 bits of tuning frequency, MSB is first
# attenuation - binary 0, 10, 20, 30 for needed attenuation
#
# RX data, to PC, 508 bytes total
# Preamble + 'RX0' + FW1 + FW2 + CLIP + 2 zeroes + 492 bytes IQ data
#
# Where:
# FW1 and FW2 - char digits firmware version number
# CLIP - ADC overflow indicator, 0 or 1 binary
# IQ data for 0 - 7 rate:
# 82 IQ pairs formatted as "I2 I1 I0 Q2 Q1 Q0.... ", MSB is first, 24 bits per sample
# IQ data for 8 - 10 rate:
# 123 IQ pairs formatted as "I1 I0 Q1 Q0..... ", MSB is first, 16 bits per sample
#
# Band Scope control, to device, 32 bytes total
# Preamble + 'BS0' + enable + period + 19 binary zeroes
#
# Where period is the full frame period in ms, from 50 to 255ms, 100ms is recommended
# for 10Hz refresh rate window.
#
# Band Scope data, to PC, 16384 16bit samples, 32768 bytes by 492 in each packet
# Preamble + 'BS0' + FW1 + FW2 + CLIP + PN + 1 zero + 492 bytes BS data
#
# Where PN is packet number 0, 1, 2, ..., 66
# BS data in format "BS1, BS0, BS1, BS0, ...", MSB is first
#
# 66 packets with PN = 0 - 65 contain 492 bytes each, and 67-th packet with PN = 66 contains
# the remaining 296 bytes of data and junk data to full 492 bytes size
#
class Hardware(BaseHardware):
sample_rates = [48, 96, 192, 240, 384, 480, 640 ,768, 960, 1536, 1920]
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.device = None
self.usb = None
self.rf_gain_labels = ('RF +10', 'RF 0', 'RF -10', 'RF -20')
self.index = 1
self.enable = 0
self.rate = 0
self.att = 10
self.freq = 7220000
self.old_freq = 0
self.sdrmicron_clock = 76800000
self.sdrmicron_decim = 1600
self.bscope_data = bytearray(0)
self.fw_ver = None
self.frame_msg = ''
if conf.fft_size_multiplier == 0:
conf.fft_size_multiplier = 3 # Set size needed by VarDecim
rx_bytes = 3 # rx_bytes is the number of bytes in each I or Q sample: 1, 2, 3, or 4
rx_endian = 1 # rx_endian is the order of bytes in the sample array: 0 == little endian; 1 == big endian
self.InitSamples(rx_bytes, rx_endian) # Initialize: read samples from this hardware file and send them to Quisk
bs_bytes = 2
bs_endian = 1
self.InitBscope(bs_bytes, bs_endian, self.sdrmicron_clock, 16384) # Initialize bandscope
def open(self): # This method must return a string showing whether the open succeeded or failed.
enum = d2xx.createDeviceInfoList() # quantity of FTDI devices
if(enum==0):
return 'Device was not found'
for i in range(enum): # Searching and openinq needed device
a = d2xx.getDeviceInfoDetail(i)
if(a['description']==b'SDR-Micron'):
try: self.usb = d2xx.openEx(a['serial'])
except:
return 'Device was not found'
Mode = 64 # Configure FT2232H into 0x40 Sync FIFO Mode
self.usb.setBitMode(255, 0) # reset
time.sleep(0.1)
self.usb.setBitMode(255, Mode) #configure FT2232H into Sync FIFO mode
self.usb.setTimeouts(100, 100) # read, write
self.usb.setLatencyTimer(2)
self.usb.setUSBParameters(32, 32) # in_tx_size, out_tx_size
time.sleep(1.5) # waiting for initialisation device
data = self.usb.read(self.usb.getQueueStatus()) # clean the usb data buffer
self.device = 'Opened'
self.frame_msg = a['description'].decode('utf-8') + ' S/N - ' + a['serial'].decode('utf-8')
return self.frame_msg
return 'Device was not found'
def close(self):
if(self.usb):
if(self.device=='Opened'):
enable = 0
self.device = None
self.rx_control_upd()
time.sleep(0.5)
self.usb.setBitMode(255, 0) # reset
self.usb.close()
def OnButtonRfGain(self, event):
btn = event.GetEventObject()
n = btn.index
self.att = n * 10
self.rx_control_upd()
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
if vfo:
self.freq = (vfo - self.transverter_offset)
if(self.freq!=self.old_freq):
self.old_freq = self.freq
self.rx_control_upd()
return tune, vfo
def ChangeBand(self, band):
# band is a string: "60", "40", "WWV", etc.
BaseHardware.ChangeBand(self, band)
btn = self.application.BtnRfGain
if btn:
if band in ('160', '80', '60', '40'):
btn.SetLabel('RF -10', True)
elif band in ('20',):
btn.SetLabel('RF 0', True)
else:
btn.SetLabel('RF +10', True)
def VarDecimGetChoices(self): # Return a list/tuple of strings for the decimation control.
return list(map(str, self.sample_rates)) # convert integer to string
def VarDecimGetLabel(self): # return a text label for the control
return "Sample rate ksps"
def VarDecimGetIndex(self): # return the current index
return self.index
def VarDecimSet(self, index=None): # return sample rate
if index is None: # initial call to set the sample rate before the call to open()
rate = self.application.vardecim_set
try:
self.index = self.sample_rates.index(rate // 1000)
except:
self.index = 0
else:
self.index = index
rate = self.sample_rates[self.index] * 1000
self.rate = self.index
if(rate>=960000):
rx_bytes = 2
rx_endian = 1
self.InitSamples(rx_bytes, rx_endian)
else:
rx_bytes = 3
rx_endian = 1
self.InitSamples(rx_bytes, rx_endian)
self.rx_control_upd()
return rate
def VarDecimRange(self): # Return the lowest and highest sample rate.
return (48000, 1920000)
def StartSamples(self): # called by the sound thread
self.enable = 1
self.rx_control_upd()
self.bscope_control_upd()
def StopSamples(self): # called by the sound thread
self.enable = 0
self.rx_control_upd()
self.bscope_control_upd()
def rx_control_upd(self):
if(self.device=='Opened'):
work = self.freq
freq4 = work & 0xFF
work = work >> 8
freq3 = work & 0xFF
work = work >> 8
freq2 = work & 0xFF
work = work >> 8
freq1 = work & 0xFF
if sys.version_info.major <= 2:
MESSAGE = 7*chr(0x55) + chr(0xd5) + 'RX0' + chr(self.enable) + chr(self.rate)
MESSAGE += chr(freq1) + chr(freq2) + chr(freq3) + chr(freq4) + chr(self.att) + 14*chr(0)
else:
MESSAGE = b"\x55\x55\x55\x55\x55\x55\x55\xd5RX0"
MESSAGE += bytes((self.enable, self.rate, freq1, freq2, freq3, freq4, self.att))
MESSAGE += bytes(14)
try: self.usb.write(MESSAGE)
except: print('Error while rx_control_upd')
def bscope_control_upd(self):
if self.device == 'Opened':
if sys.version_info.major <= 2:
MESSAGE = 7*chr(0x55) + chr(0xd5) + 'BS0' + chr(self.enable) + chr(100) + 19 * chr(0)
else:
MESSAGE = b"\x55\x55\x55\x55\x55\x55\x55\xd5BS0"
MESSAGE += bytes((self.enable, 100))
MESSAGE += bytes(19)
try: self.usb.write(MESSAGE)
except: None
def GetRxSamples(self): # Read all data from the SDR Micron and process it.
if self.device == None:
return
while (self.usb.getQueueStatus() >= 508):
data = self.usb.read(508)
data = bytearray(data)
if data[8:11] == bytearray(b'RX0'): # Rx I/Q data
if data[13]:
self.GotClip()
if self.fw_ver is None:
self.fw_ver = chr(data[11]) + '.' + chr(data[12])
self.frame_msg += ' F/W version - ' + self.fw_ver
self.application.main_frame.SetConfigText(self.frame_msg)
self.AddRxSamples(data[16:])
elif data[8:11] == bytearray(b'BS0'): # bandscope data
packet_number = data[14]
if packet_number == 0: # start of a block of data
self.bscope_data = data[16:] # 492 bytes
elif packet_number < 66:
self.bscope_data += data[16:] # 492 bytes
else: # end of a block of data, 296 bytes
self.bscope_data += data[16:312]
self.AddBscopeSamples(self.bscope_data)

View File

@ -8,7 +8,7 @@ import struct
# 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.1.52'
Version = '4.1.53'
fp = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version)

Binary file not shown.

19
softrock_tune_vfo.py Executable file
View File

@ -0,0 +1,19 @@
# This is a replacement hardware file for the Softrock and similar radios.
# Normally Quisk will change the VFO (center frequency) by large amounts, and perform
# fine tuning within the returned bandwidth. This hardware file does all tuning with the VFO.
# This creates a constant offset between the VFO and the tuning frequency. Specify this file
# as your hardware file on the Config/radio/Hardware screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import softrock
from softrock.hardware_usb import Hardware as BaseHardware
class Hardware(BaseHardware):
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
vfo_freq = tx_freq - 10000
tx, vfo = BaseHardware.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
return tx, vfo