Updated local repository to 4.1.54

This commit is contained in:
Rob French 2020-04-29 22:09:06 -05:00
parent 4d5471cc5d
commit 65732f3d1a
19 changed files with 1175 additions and 15 deletions

View File

@ -1,3 +1,12 @@
Quisk Version 4.1.54 March 2020
================================
There are now two new radios available in Quisk. David Fainitski contributed code for the Sdr Micron,
and Andrea Montefusco IW0HDV contributed code for the Perseus SDR. The radios should appear on the
list of supported radios in Config/Radios screen. Check the Config/radio/Hardware screen to see if you
need any more options. Please test these radios to make sure everything is working.
I added a general way to add new radios to the configuration screens. See docs.html under Custom Hardware.
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
@ -8,7 +17,7 @@ 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.
different Hermes hardware such as Red Pitaya.
Quisk Version 4.1.52 December 2019
===================================

View File

@ -7,4 +7,6 @@ graft n2adr
graft sdriqpkg
graft softrock
graft soapypkg
graft sdrmicronpkg
graft perseuspkg
global-exclude *.pyc

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.53
Version: 4.1.54
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.53
#Quisk version 4.1.54

View File

@ -157,8 +157,16 @@ class Configuration:
try:
if fmt4 == 'text': # Note: JSON returns Unicode strings !!!
setattr(conf, k, v)
elif fmt4 in ('dict', 'list'):
setattr(conf, k, v)
elif fmt4 == 'dict':
if isinstance(v, dict):
setattr(conf, k, v)
else:
raise ValueError()
elif fmt4 == 'list':
if isinstance(v, list):
setattr(conf, k, v)
else:
raise ValueError()
elif fmt4 == 'inte':
setattr(conf, k, int(v, base=0))
elif fmt4 == 'numb':
@ -448,13 +456,27 @@ class Configuration:
# Then some help text starting with "# "
# Then a list of possible value#explain with the default first
# Then a blank line to end.
self.format4name = {}
self.format4name['hardware_file_type'] = 'text'
self._ParserConf('quisk_conf_defaults.py')
# Read any user-defined radio types
for dirname in os.listdir('.'):
if not os.path.isdir(dirname) or dirname[-3:] != 'pkg':
continue
if dirname in ('freedvpkg', 'sdriqpkg', 'soapypkg'):
continue
filename = os.path.join(dirname, 'quisk_hardware.py')
if not os.path.isfile(filename):
continue
try:
self._ParserConf(filename)
except:
traceback.print_exc()
def _ParserConf(self, filename):
re_AeqB = re.compile("^#?(\w+)\s*=\s*([^#]+)#*(.*)") # item values "a = b"
section = None
data_name = None
fp = open("quisk_conf_defaults.py", "r")
fp = open(filename, "r")
for line in fp:
line = line.strip()
if not line:
@ -486,7 +508,7 @@ class Configuration:
value_list = []
if data_name in self.format4name:
if self.format4name[data_name] != fmt:
print ("Inconsistent format for", data_name, self.format4name[data_name], fmt)
print (filename, ": Inconsistent format for", data_name, self.format4name[data_name], fmt)
else:
self.format4name[data_name] = fmt
section_data.append([data_name, dspl, fmt, '', value_list])
@ -495,7 +517,7 @@ class Configuration:
mo = re_AeqB.match(line)
if mo:
if data_name != mo.group(1):
print ("Parse error for", data_name)
print (filename, ": Parse error for", data_name)
continue
value = mo.group(2).strip()
expln = mo.group(3).strip()

View File

@ -50,6 +50,7 @@ div.contents {
<li><a href="#Configuration">Configuration</a></li>
<li><a href="#SoundCards">Sound Cards</a></li>
<li><a href="#SDRIQ">SDR-IQ</a></li>
<li><a href="#Perseus">Perseus</a></li>
<li><a href="#Timing">Timing</a></li>
<li><a href="#USBControl">USB Control</a></li>
<li><a href="#CustomHardware">Custom Hardware</a></li>
@ -80,6 +81,7 @@ complete transceiver. Quisk works with this hardware:
<li>SoftRock connected to the sound card</li>
<li>Many other SDR's connected to the sound card</li>
<li>SDR-IQ connected by USB</li>
<li>Perseus connected by USB</li>
<li>N2ADR hardware connected by Ethernet and IP</li>
<li>HiQSDR hardware connected by Ethernet and IP</li>
<li>The Hermes-Lite project at <a href="http://hermeslite.com">hermeslite.com</a></li>
@ -874,6 +876,35 @@ In earlier versions of Windows, port names are COM1, COM2 etc. and use the "USB
Windows should find this driver by itself.
</p>
<br>
<h2 id="Perseus">Perseus as Input </h2>
<p>
Quisk can use an Perseus HF receiver from Microtelecom instead of a sound card as input.
Set up a radio of type Perseus. The Perseus uses a native USB interface to connect to Quisk.
The Quisk perseuspkg extension relies on <a href="http://github.com/Microtelecom/libpserseus-sdr">libperseus-sdr</a>
open source library to manage Perseus hardware and receive the I/Q samples stream.
</p>
<br>
<p>
Follow the instruction into GitHub repository to compile and install the library.
On Suse distribution the library is available as binary package.
Next compile the perseuspkg using the command:
</p>
<br>
<pre>
make perseus3
</pre>
</br>
<p>
The several sample rates can be selected opening Config panel: in
the Config tab there is the Samples rates dropdown.
</p>
The input analog filter can be switched in using the button Wideband.<br>
The input attenuator is operate via the button RF, that allows to select
the four attenuator steps.<br>
The ADC commands for dithering and preamplifier are found on
left bottom corner as ADC Dither and ADC Preamp.<br>
</p>
<br>
<h2 id="Timing">Timing </h2>
<br>
There are several configuration parameters devoted to tuning; read the
@ -988,6 +1019,7 @@ can put code there to poll a serial port or to perform other
housekeeping functions (try to be efficient). The two remaining
functions deserve more documentation.
<br>
<br>
<h3 id="g0.8.1">ChangeFrequency(self, tune, vfo, source='', band='', event=None)</h3>
Quisk calls the ChangeFrequency() function when the user changes the Tx
frequency with a mouse click on the graph or waterfall, with the entry
@ -1117,6 +1149,29 @@ return the new frequencies from ReturnFrequency() or else Quisk will be
unaware of the change.
<br>
<br>
<h3 id="g0.8.3">Adding Custom Hardware to the Config/Radios Screen</h3>
Once you write your own hardware file for your custom hardware, you still need to add it to the
list of available radio types, and add its configuration options to its Config/radios/Hardware screen.
First create a subdirectory of the Quisk directory with a name ending in "pkg"; for example,
"myradiopkg". Then put your hardware file in this subdirectory with the name "quisk_hardware.py".
You will need at least one configuration option to specify the hardware file name.
Add this code (all comments) near the top of quisk_hardware.py:
<pre>
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers MyRadio, The special radio that I own
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'myradiopkg/quisk_hardware.py'
</pre>
Of course, change the names of the radio and subdirectory as appropriate. Your radio of general type "MyRadio"
will now appear in the list of radios on the Config/Radios screen, and its configuration items will appear
on the Config/radio/Hardware screen. You can add additional items. See quisk_conf_defaults.py and the *pkg
subdirectories for examples.
<br>
<br>
<h2 id="ExtensionPackages">Extension Packages </h2>
Quisk comes with two extension packages. The freedvpkg package

View File

@ -7,11 +7,13 @@ quisk2:
python2 setup.py build_ext --force --inplace
@echo
@echo 'Use "make soapy2" to make the Python2 soapy module'
@echo 'Use "make perseus2" to make the Python2 perseus package'
quisk3:
python3 setup.py build_ext --force --inplace
@echo
@echo 'Use "make soapy3" to make the Python3 soapy module'
@echo 'Use "make perseus3" to make the Python3 perseus package'
soapy2:
(cd soapypkg; make soapy2)
@ -19,5 +21,11 @@ soapy2:
soapy3:
(cd soapypkg; make soapy3)
perseus2:
(cd perseuspkg; make perseus2)
perseus3:
(cd perseuspkg; make perseus3)
macports:
env ARCHFLAGS="-arch x86_64" python setup.py build_ext --force --inplace -D USE_MACPORTS

10
perseuspkg/README.txt Executable file
View File

@ -0,0 +1,10 @@
Microtelecom Perseus HF receiver
Prerequisite are:
libusb-1.0-0-dev (found in all major Linux distro)
libperseus-sdr as found in https://github.com/Microtelecom/libperseus-sdr/archive/master.zip

1
perseuspkg/__init__.py Executable file
View File

@ -0,0 +1 @@
#

7
perseuspkg/makefile Executable file
View File

@ -0,0 +1,7 @@
perseus2:
python2 setup.py build_ext --force --inplace
perseus3:
python3 setup.py build_ext --force --inplace

498
perseuspkg/perseus.c Executable file
View File

@ -0,0 +1,498 @@
/*
*
* Microtelecom perseus HF receiver
*
* access module: exposes Python functions needed in quisk_hardware.py
* to control hardware
*
*/
#include <Python.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <complex.h>
#include <perseus-sdr.h>
#define IMPORT_QUISK_API
#include "quisk.h"
#include "filter.h"
// This module was written by Andrea Montefusco IW0HDV.
typedef union {
struct {
int32_t i;
int32_t q;
} __attribute__((__packed__)) iq;
struct {
uint8_t i1;
uint8_t i2;
uint8_t i3;
uint8_t i4;
uint8_t q1;
uint8_t q2;
uint8_t q3;
uint8_t q4;
} __attribute__((__packed__)) ;
} iq_sample;
// buffer size for libperseus-sdr
const static int nb = 6;
const static int bs = 1024;
// This module uses the Python interface to import symbols from the parent _quisk
// extension module. It must be linked with import_quisk_api.c. See the documentation
// at the start of import_quisk_api.c.
#define DEBUG 1
static int num_perseus = 0;
static perseus_descr *descr = 0;
static int sr = 48000;
static float freq = 7050000.0;
static int adc_dither = 0;
static int adc_preamp = 0;
static void quisk_stop_samples(void);
static const char *fname = "/tmp/quiskperseus";
static int rfd = 0;
static int wfd = 0;
static int running = 0;
static int wb_filter = 0;
// Called in a loop to read samples; called from the sound thread.
static int quisk_read_samples(complex double * cSamples)
{
//fprintf (stderr, "r"); fflush(stderr);
int n = read(rfd, cSamples, sizeof(complex double)*SAMP_BUFFER_SIZE);
//fprintf(stderr, "%d ", n);
if (n >= 0)
return n/sizeof(complex double); // return number of samples
else
return 0;
}
// Called in a loop to write samples; called from the sound thread.
static int quisk_write_samples(complex double * cSamples, int nSamples)
{
return 0;
}
//
// callback that writes in the output pipe IQ values as
// complex floating point
//
static int user_data_callback_c_f(void *buf, int buf_size, void *extra)
{
// The buffer received contains 24-bit IQ samples (6 bytes per sample)
// Here we save the received IQ samples as 32 bit
// (msb aligned) integer IQ samples.
uint8_t *samplebuf = (uint8_t*)buf;
int nSamples = buf_size/6;
int k;
iq_sample s;
// the 24 bit data is scaled to a 32bit value (so that the machine's
// natural signed arithmetic will work)
for (k=0; k < nSamples; k++) {
s.i1 = s.q1 = 0;
s.i2 = *samplebuf++;
s.i3 = *samplebuf++;
s.i4 = *samplebuf++;
s.q2 = *samplebuf++;
s.q3 = *samplebuf++;
s.q4 = *samplebuf++;
// move I/Q to complex number
complex double x = (double)(s.iq.i)*10 + (double)(s.iq.q)*10 * _Complex_I;
if (wfd > 0) {
int n = write(wfd, &x, sizeof(complex double));
if (n<0 && ! -EAGAIN )
fprintf(stderr, "perseus c: Can't write output file: %s, descriptor: %d\n", strerror(errno), wfd);
}
}
return 0;
}
// Start sample capture; called from the sound thread.
static void quisk_start_samples(void)
{
fprintf (stderr, "perseus c: quisk_start_samples\n"); fflush(stderr);
int rc = mkfifo(fname, 0666);
if ((rc == -1) && (errno != EEXIST)) {
perror("perseus c: Error creating the named pipe");
}
rfd = open(fname, O_RDONLY|O_NONBLOCK);
if (rfd < 0) fprintf(stderr, "perseus c: Can't open read FIFO (%s)\n", strerror(errno));
else fprintf(stderr, "perseus c: read FIFO (%d)\n", rfd);
wfd = open(fname, O_WRONLY|O_NONBLOCK);
if (wfd < 0) fprintf(stderr, "perseus c: Can't open write FIFO (%s)\n", strerror(errno));
else fprintf(stderr, "perseus c: write FIFO (%d)\n", wfd);
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
} else {
fprintf(stderr, "perseus c: sampling rate set to: %d\n", sr);
// Re-enable preselection filters (WB_MODE Off)
perseus_set_ddc_center_freq(descr, freq, wb_filter);
// start sampling ops
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
} else
fprintf(stderr, "perseus c: start async input\n");
running = 1;
}
}
// Stop sample capture; called from the sound thread.
static void quisk_stop_samples(void)
{
fprintf (stderr, "perseus c: quisk_stop_samples\n"); fflush(stderr);
// We stop the acquisition...
fprintf(stderr, "perseus c: stopping async data acquisition...\n");
perseus_stop_async_input(descr);
running = 0;
// clearing FIFO...
close(rfd);
close(wfd);
unlink(fname);
}
// Called to close the sample source; called from the GUI thread.
static PyObject * close_device(PyObject * self, PyObject * args)
{
fprintf (stderr, "perseus c: close_device\n");
int sample_device; // for now one only Perseus can be managed
if (!PyArg_ParseTuple (args, "i", &sample_device))
return NULL;
if (descr) {
// We stop the acquisition...
if (running) {
perseus_stop_async_input(descr);
running = 0;
}
perseus_close(descr);
descr = 0;
}
Py_INCREF (Py_None);
return Py_None;
}
// Called to open the Perseus SDR device; called from the GUI thread.
static PyObject * open_device(PyObject * self, PyObject * args)
{
char buf128[128] = "Capture Microtelecom Perseus HF receiver";
eeprom_prodid prodid;
fprintf (stderr, "perseus c: open device (%d)\n", num_perseus); fflush(stderr);
// Check how many Perseus receivers are connected to the system
if (num_perseus == 0) num_perseus = perseus_init();
fprintf(stderr, "perseus c: %d Perseus receivers found\n",num_perseus);
if (num_perseus == 0) {
sprintf(buf128, "No Perseus receivers detected\n");
perseus_exit();
goto main_cleanup;
}
// Open the first one...
if ((descr = perseus_open(0)) == NULL) {
sprintf(buf128, "error: %s\n", perseus_errorstr());
fprintf(stderr, "perseus c: open error: %s\n", perseus_errorstr());
goto main_cleanup;
}
// Download the standard firmware to the unit
fprintf(stderr, "perseus c: Downloading firmware...\n");
if (perseus_firmware_download(descr,NULL)<0) {
sprintf(buf128, "perseus c: firmware download error: %s", perseus_errorstr());
goto main_cleanup;
}
// Dump some information about the receiver (S/N and HW rev)
if (perseus_is_preserie(descr, 0) == PERSEUS_SNNOTAVAILABLE)
fprintf(stderr, "perseus c: The device is a preserie unit");
else
if (perseus_get_product_id(descr,&prodid)<0)
fprintf(stderr, "perseus c: get product id error: %s", perseus_errorstr());
else
fprintf(stderr, "perseus c: Receiver S/N: %05d-%02hX%02hX-%02hX%02hX-%02hX%02hX - HW Release:%hd.%hd\n",
(uint16_t) prodid.sn,
(uint16_t) prodid.signature[5],
(uint16_t) prodid.signature[4],
(uint16_t) prodid.signature[3],
(uint16_t) prodid.signature[2],
(uint16_t) prodid.signature[1],
(uint16_t) prodid.signature[0],
(uint16_t) prodid.hwrel,
(uint16_t) prodid.hwver);
// Printing all sampling rates available .....
{
int buf[BUFSIZ];
if (perseus_get_sampling_rates (descr, buf, sizeof(buf)/sizeof(buf[0])) < 0) {
fprintf(stderr, "perseus c: get sampling rates error: %s\n", perseus_errorstr());
goto main_cleanup;
} else {
int i = 0;
while (buf[i]) {
fprintf(stderr, "perseus c: #%d: sample rate: %d\n", i, buf[i]);
i++;
}
}
}
// Configure the receiver for 2 MS/s operations
fprintf(stderr, "perseus c: Configuring FPGA...\n");
if (perseus_set_sampling_rate(descr, sr) < 0) { // specify the sampling rate value in Samples/second
//if (perseus_set_sampling_rate_n(descr, 0)<0) // specify the sampling rate value as ordinal in the vector
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
goto main_cleanup;
}
// ADC settings
perseus_set_adc (descr, adc_dither, adc_preamp);
// Disable preselection filters (WB_MODE On)
//perseus_set_ddc_center_freq(descr, freq, 0);
//sleep(1);
// Re-enable preselection filters (WB_MODE Off)
perseus_set_ddc_center_freq(descr, freq, wb_filter);
quisk_sample_source4(&quisk_start_samples, &quisk_stop_samples, &quisk_read_samples, &quisk_write_samples);
fprintf (stderr, "perseus c: quisk sample source callbacks established\n"); fflush(stderr);
goto exit_success;
main_cleanup:
return PyString_FromString("ERROR");
exit_success:
return PyString_FromString(buf128);
}
static PyObject * set_frequency(PyObject * self, PyObject * args) // Called from GUI thread
{
float param;
if (!PyArg_ParseTuple (args, "f", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: set DDC frequency%lf\n", param);
freq= param;
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter == 0);
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_input_filter(PyObject * self, PyObject * args) // Called from GUI thread
{
int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: set input filter%d\n", param);
wb_filter = param;
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter == 0);
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_sampling_rate(PyObject * self, PyObject * args) // Called from GUI thread
{
int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
fprintf (stderr, "perseus c: Set sampling rate %d\n", param);
if (param < 48000) sr = param * 1000;
else sr = param;
if (descr) {
if (running) {
fprintf(stderr, "perseus c: stop async input\n");
perseus_stop_async_input(descr);
}
// specify the sampling rate value in Samples/secon
if (perseus_set_sampling_rate(descr, sr) < 0) {
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
}
if (running) {
if (perseus_start_async_input(descr, nb*bs, user_data_callback_c_f, 0)<0) {
fprintf(stderr, "perseus c: start async input error: %s\n", perseus_errorstr());
} else
fprintf(stderr, "perseus c: start async input @%d\n", sr);
}
} else {
fprintf(stderr, "perseus c: tryng to start async input with no device open\n");
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_attenuator(PyObject * self, PyObject * args) // Called from GUI thread
{ int param;
if (!PyArg_ParseTuple (args, "i", &param))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set attenuator %d\n", param);
if (descr) {
// specify the sampling rate value in Samples/secon
if (perseus_set_attenuator_n(descr, (int)(param / -10)) < 0) {
fprintf(stderr, "perseus c: fpga configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
// Enable ADC Dither, Disable ADC Preamp
// perseus_set_adc(descr, true, false);
static PyObject * set_adc_dither (PyObject * self, PyObject * args) // Called from GUI thread
{ int dither_;
if (!PyArg_ParseTuple (args, "i", &dither_))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set ADC: dither %d\n", dither_);
adc_dither = dither_;
if (descr) {
// specify the ADC dithering
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * set_adc_preamp (PyObject * self, PyObject * args) // Called from GUI thread
{ int preamp_;
if (!PyArg_ParseTuple (args, "i", &preamp_))
return NULL;
if (DEBUG)
fprintf (stderr, "perseus c: Set ADC: preamp: %d\n", preamp_);
adc_preamp = preamp_;
if (descr) {
// specify the sampling rate value in Samples/secon
if (perseus_set_adc(descr, adc_dither == 1, adc_preamp == 1) < 0) {
fprintf(stderr, "perseus c: ADC configuration error: %s\n", perseus_errorstr());
}
}
Py_INCREF (Py_None);
return Py_None;
}
static PyObject * deinit(PyObject * self, PyObject * args) // Called from dctor
{
perseus_exit();
Py_INCREF (Py_None);
return Py_None;
}
// Functions callable from Python are listed here:
static PyMethodDef QuiskMethods[] = {
{"open_device", open_device, METH_VARARGS, "Open the hardware."},
{"close_device", close_device, METH_VARARGS, "Close the hardware"},
{"set_frequency", set_frequency, METH_VARARGS, "set frequency"},
{"set_input_filter", set_input_filter, METH_VARARGS, "set input filter"},
{"set_sampling_rate", set_sampling_rate, METH_VARARGS, "set sampling rate"},
{"set_attenuator", set_attenuator, METH_VARARGS, "set attenuator"},
{"set_adc_dither", set_adc_dither, METH_VARARGS, "set ADC dither"},
{"set_adc_preamp", set_adc_preamp, METH_VARARGS, "set ADC preamplifier"},
{"deinit", deinit, METH_VARARGS, "deinit"},
// {"get_device_list", get_device_list, METH_VARARGS, "Return a list of Perseus SDR devices"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
#if PY_MAJOR_VERSION < 3
// Python 2.7:
// Initialization, and registration of public symbol "initperseus":
PyMODINIT_FUNC initperseus (void)
{
if (Py_InitModule ("perseus", QuiskMethods) == NULL) {
fprintf(stderr, "perseus c: Py_InitModule failed!\n");
return;
}
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
return; //Error
}
}
// Python 3:
#else
static struct PyModuleDef perseusmodule = {
PyModuleDef_HEAD_INIT,
"perseus",
NULL,
-1,
QuiskMethods
} ;
PyMODINIT_FUNC PyInit_perseus(void)
{
PyObject * m;
m = PyModule_Create(&perseusmodule);
if (m == NULL)
return NULL;
// Import pointers to functions and variables from module _quisk
if (import_quisk_api()) {
fprintf(stderr, "perseus c: Failure to import pointers from _quisk\n");
return m; //Error
}
return m;
}
#endif

187
perseuspkg/quisk_hardware.py Executable file
View File

@ -0,0 +1,187 @@
# This is the hardware file to support radios accessed by the PerseusSDR interface.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import socket, traceback, time, math
import _quisk as QS
try:
from perseuspkg import perseus
except:
#traceback.print_exc()
perseus = None
print ("Error: Perseus package not found.\n")
from quisk_hardware_model import Hardware as BaseHardware
DEBUG = 1
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers PerseusSDR, The PerseusSDR interface to multiple hardware SDRs.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'perseuspkg/quisk_hardware.py'
## widgets_file_name Widget file path, rfile
# This optional file adds additional controls for the radio.
#widgets_file_name = 'perseuspkg/quisk_widgets.py'
class Hardware(BaseHardware):
def __init__(self, app, conf):
BaseHardware.__init__(self, app, conf)
self.rf_gain_labels = ('RF +0', 'RF -10', 'RF -20', 'RF -30')
self.antenna_labels = ('Wide Band', 'Band Filter')
self.vardecim_index = 0
self.fVFO = 0.0 # Careful, this is a float
print ("__init__: %s" % conf)
self.rates = [ 48000, \
95000, \
96000, \
125000, \
192000, \
250000, \
500000, \
1000000, \
1600000, \
2000000 \
]
self.current_rate = 192000
self.att = 0;
self.wb = 0
def __del__(self):
# try to clear hardware
if perseus:
perseus.close()
perseus.deinit()
def get_hw (self):
return perseus
def pre_open(self):
print ("pre_open")
pass
def set_parameter(self, *args):
pass
def open(self): # Called once to open the Hardware
if not perseus:
return "Perseus module not available"
txt = perseus.open_device("perseus",2,3)
print ("perseus hardware: open")
return txt
def close(self): # Called once to close the Hardware
print ("perseus hardware: close")
if perseus:
perseus.close_device(1)
def ChangeGain(self, rxtx): # rxtx is '_rx' or '_tx'
if not perseus:
return
print ("perseus hardware: ChangeGain", rxtx)
pass
def OnButtonRfGain(self, event):
#btn = event.GetEventObject()
n = event.GetEventObject().index
self.att = n * -10
print ("perseus hardware: OnButtonRfGain: %d new attenuation: %d" % (n, self.att))
perseus.set_attenuator (self.att)
def ChangeFrequency(self, tune, vfo, source='', band='', event=None):
fVFO = float(vfo)
if self.fVFO != fVFO:
self.fVFO = fVFO
perseus.set_frequency(fVFO)
return tune, vfo
def ReturnFrequency(self):
# Return the current tuning and VFO frequency. If neither have changed,
# you can return (None, None). This is called at about 10 Hz by the main.
# return (tune, vfo) # return changed frequencies
return None, None # frequencies have not changed
def ReturnVfoFloat(self):
# Return the accurate VFO frequency as a floating point number.
# You can return None to indicate that the integer VFO frequency is valid.
return self.fVFO
# def OnBtnFDX(self, fdx): # fdx is 0 or 1
# pass
#
# def OnButtonPTT(self, event):
# pass
#
# def OnSpot(self, level):
# # level is -1 for Spot button Off; else the Spot level 0 to 1000.
# pass
#
# def ChangeMode(self, mode): # Change the tx/rx mode
# # mode is a string: "USB", "AM", etc.
# pass
#
# def ChangeBand(self, band):
# pass
#
# def HeartBeat(self): # Called at about 10 Hz by the main
# pass
def ImmediateChange(self, name, value):
print ("perseus hardware: ImmediateChange: perseus: name: %s value: %s" % (name, value))
if name == 'perseus_setSampleRate_rx':
value = int(value)
self.application.OnBtnDecimation(rate=value)
perseus.set_sampling_rate(value)
self.curren_dec = value
def VarDecimGetChoices(self): # Not used to set sample rate
print ("perseus hardware: VarDecimGetChoices")
return list(map(str, self.rates)) # convert integer to string
def VarDecimGetLabel(self): # Return a text label for the decimation control.
return 'Sample rates: '
def VarDecimGetIndex(self): # Return the index 0, 1, ... of the current decimation.
for i in range(len(self.rates)):
if self.rates[i] == self.current_rate:
return i
return 0
def VarDecimSet(self, index=None): # Called when the control is operated; if index==None, called on startup.
print ("perseus hardware: VarDecimSet: index: %s" % (index))
if index == None:
print ("perseus hardware: VarDecimSet: current sampling rate: %d" % self.current_rate)
new_rate = self.current_rate
else:
new_rate = self.rates[index]
print ("perseus hardware: VarDecimSet: New sampling rate: %d" % new_rate)
perseus.set_sampling_rate(int(new_rate))
return int(new_rate)
def VarDecimRange(self): # Return the lowest and highest sample rate.
print ("perseus hardware: VarDecimRange: %s" % self.rates)
return (self.rates[0], self.rates[-1])
def OnButtonAntenna(self, event):
btn = event.GetEventObject()
self.wb_filter = n = btn.index
print ("OnButtonAntenna: %d" % n)
perseus.set_input_filter (self.wb_filter)
# def StartSamples(self): # called by the sound thread
# print("perseus hardware: StartSamples")
# def StopSamples(self): # called by the sound thread
# print("perseus hardware: StopSamples")

37
perseuspkg/quisk_widgets.py Executable file
View File

@ -0,0 +1,37 @@
# Please do not change this widgets module for Quisk. Instead copy
# it to your own quisk_widgets.py and make changes there.
#
# This module is used to add extra widgets to the QUISK screen.
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import math, wx
class BottomWidgets: # Add extra widgets to the bottom of the screen
def __init__(self, app, hardware, conf, frame, gbs, vertBox):
self.config = conf
self.hardware = hardware
self.application = app
self.start_row = app.widget_row # The first available row
self.start_col = app.button_start_col # The start of the button columns
self.Widgets_0x06(app, hardware, conf, frame, gbs, vertBox)
def Widgets_0x06(self, app, hardware, conf, frame, gbs, vertBox):
self.num_rows_added = 1
start_row = self.start_row
b1 = app.QuiskCheckbutton(frame, self.OnADC_dither, 'ADC Dither')
gbs.Add(b1, (start_row, self.start_col), (1, 2), flag=wx.EXPAND)
b2 = app.QuiskCheckbutton(frame, self.OnADC_preamp, 'ADC Preamp')
gbs.Add(b2, (start_row, self.start_col + 2), (1, 2), flag=wx.EXPAND)
def OnADC_dither(self, event):
btn = event.GetEventObject()
value = btn.GetValue()
self.hardware.get_hw().set_adc_dither (value)
def OnADC_preamp(self, event):
btn = event.GetEventObject()
value = btn.GetValue()
self.hardware.get_hw().set_adc_preamp (value)

48
perseuspkg/setup.py Executable file
View File

@ -0,0 +1,48 @@
from distutils.core import setup, Extension
import sys
module2 = Extension ('perseus',
libraries = ['m', 'perseus-sdr'],
sources = ['../import_quisk_api.c', 'perseus.c'],
include_dirs = ['.', '..'],
)
modulew2 = Extension ('perseus',
sources = ['../import_quisk_api.c', 'perseus.c'],
include_dirs = ['.', '..'],
libraries = ['WS2_32', 'perseus-sdr'],
)
if sys.platform == "win32":
Modules = [modulew2]
else:
Modules = [module2]
setup (name = 'perseus',
version = '0.1',
description = 'perseus is an extension to Quisk to support Microtelecom Perseus SDR hardware',
long_description = """Microtelecom Perseus SDR HF receiver.
""",
author = 'Andrea Montefusco IW0HDV',
author_email = 'andrew@montefusco.com',
url = 'http://www.montefusco.com',
download_url = 'http://james.ahlstrom.name/quisk/',
packages = ['quisk.perseuspkg'],
package_dir = {'perseus' : '.'},
ext_modules = Modules,
classifiers = [
'Development Status :: 6 - Mature',
'Environment :: X11 Applications',
'Environment :: Win32 (MS Windows)',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Operating System :: Microsoft :: Windows',
'Programming Language :: Python',
'Programming Language :: C',
'Topic :: Communications :: Ham Radio',
],
)

View File

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: quisk
Version: 4.1.53
Version: 4.1.54
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

@ -7,7 +7,6 @@ WinQuisk.pyw
WinQuiskVna.pyw
__init__.py
__main__.py
_quisk.pyd
configure.py
defaults.html
docs.html
@ -51,7 +50,6 @@ 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
@ -99,7 +97,6 @@ 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
@ -151,6 +148,11 @@ winsound.txt
./n2adr/uhf_conf.py
./n2adr/uhf_hardware.py
./n2adr/uhf_widgets.py
./perseuspkg/README.txt
./perseuspkg/__init__.py
./perseuspkg/quisk_hardware.py
./perseuspkg/quisk_widgets.py
./perseuspkg/setup.py
./sdriqpkg/README.txt
./sdriqpkg/__init__.py
./sdriqpkg/quisk_hardware.py
@ -217,6 +219,13 @@ n2adr/uhf_conf.py
n2adr/uhf_hardware.old
n2adr/uhf_hardware.py
n2adr/uhf_widgets.py
perseuspkg/README.txt
perseuspkg/__init__.py
perseuspkg/makefile
perseuspkg/perseus.c
perseuspkg/quisk_hardware.py
perseuspkg/quisk_widgets.py
perseuspkg/setup.py
quisk.egg-info/PKG-INFO
quisk.egg-info/SOURCES.txt
quisk.egg-info/dependency_links.txt
@ -228,6 +237,7 @@ sdriqpkg/quisk_hardware.py
sdriqpkg/sdriq.c
sdriqpkg/sdriq.h
sdriqpkg/sdriq.pyd
sdrmicronpkg/quisk_hardware.py
soapypkg/__init__.py
soapypkg/makefile
soapypkg/quisk_hardware.py

266
sdrmicronpkg/quisk_hardware.py Executable file
View File

@ -0,0 +1,266 @@
# -*- 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
#
# Define the name of the hardware and the items on the hardware screen (see quisk_conf_defaults.py):
################ Receivers SdrMicron, The SDR Micron project by David Fainitski.
## hardware_file_name Hardware file path, rfile
# This is the file that contains the control logic for each radio.
#hardware_file_name = 'sdrmicronpkg/quisk_hardware.py'
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.53'
Version = '4.1.54'
fp = open("__init__.py", "w") # write title string
fp.write("#Quisk version %s\n" % Version)
@ -155,7 +155,7 @@ N1MM+ and software that uses Hamlib.
author_email = 'jahlstr@gmail.com',
url = 'http://james.ahlstrom.name/quisk/',
packages = ['quisk', 'quisk.sdriqpkg', 'quisk.n2adr', 'quisk.softrock', 'quisk.freedvpkg',
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg'],
'quisk.hermes', 'quisk.hiqsdr', 'quisk.afedrinet', 'quisk.soapypkg', 'quisk.perseuspkg'],
package_dir = {'quisk' : '.'},
package_data = {'' : ['*.txt', '*.html', '*.so', '*.dll']},
entry_points = {'gui_scripts' : ['quisk = quisk.quisk:main', 'quisk_vna = quisk.quisk_vna:main']},