Updated local repository to 4.1.53
This commit is contained in:
parent
91aa2689ce
commit
4d5471cc5d
@ -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.
|
||||
|
2
PKG-INFO
2
PKG-INFO
@ -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
|
||||
|
@ -1 +1 @@
|
||||
#Quisk version 4.1.52
|
||||
#Quisk version 4.1.53
|
||||
|
1
afedrinet/af_comp.bat.makeit
Executable file
1
afedrinet/af_comp.bat.makeit
Executable 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
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
12
docs.html
12
docs.html
@ -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
|
||||
|
10
dxcluster.py
10
dxcluster.py
@ -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):
|
||||
|
@ -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
42
quisk.c
@ -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."},
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
74
quisk.py
74
quisk.py
@ -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:
|
||||
|
@ -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
122
quisk_conf_openradio.py
Executable 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
259
quisk_hardware_sdrmicron.py
Executable 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)
|
||||
|
||||
|
2
setup.py
2
setup.py
@ -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.
Binary file not shown.
19
softrock_tune_vfo.py
Executable file
19
softrock_tune_vfo.py
Executable 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
|
Loading…
Reference in New Issue
Block a user