From 65732f3d1a36012d53d9498628da043d3fa721f7 Mon Sep 17 00:00:00 2001
From: Rob French
Date: Wed, 29 Apr 2020 22:09:06 -0500
Subject: [PATCH] Updated local repository to 4.1.54
---
CHANGELOG.txt | 11 +-
MANIFEST.in | 2 +
PKG-INFO | 2 +-
__init__.py | 2 +-
configure.py | 34 +-
docs.html | 55 ++
makefile | 8 +
perseuspkg/README.txt | 10 +
perseuspkg/__init__.py | 1 +
perseuspkg/makefile | 7 +
perseuspkg/perseus.c | 498 +++++++++++++++++++
perseuspkg/quisk_hardware.py | 187 +++++++
perseuspkg/quisk_widgets.py | 37 ++
perseuspkg/setup.py | 48 ++
quisk.egg-info/PKG-INFO | 2 +-
quisk.egg-info/SOURCES.txt | 16 +-
sdrmicronpkg/quisk_hardware.py | 266 ++++++++++
setup.py | 4 +-
soapypkg/build/temp.linux-x86_64-3.6/soapy.o | Bin 89688 -> 89736 bytes
19 files changed, 1175 insertions(+), 15 deletions(-)
create mode 100755 perseuspkg/README.txt
create mode 100755 perseuspkg/__init__.py
create mode 100755 perseuspkg/makefile
create mode 100755 perseuspkg/perseus.c
create mode 100755 perseuspkg/quisk_hardware.py
create mode 100755 perseuspkg/quisk_widgets.py
create mode 100755 perseuspkg/setup.py
create mode 100755 sdrmicronpkg/quisk_hardware.py
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 75e33ab..f6e6192 100755
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -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
===================================
diff --git a/MANIFEST.in b/MANIFEST.in
index dcd2e88..2084fb0 100755
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,4 +7,6 @@ graft n2adr
graft sdriqpkg
graft softrock
graft soapypkg
+graft sdrmicronpkg
+graft perseuspkg
global-exclude *.pyc
diff --git a/PKG-INFO b/PKG-INFO
index 9163563..909e803 100755
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -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
diff --git a/__init__.py b/__init__.py
index d47ec66..9af07ff 100755
--- a/__init__.py
+++ b/__init__.py
@@ -1 +1 @@
-#Quisk version 4.1.53
+#Quisk version 4.1.54
diff --git a/configure.py b/configure.py
index 4714992..3e37718 100755
--- a/configure.py
+++ b/configure.py
@@ -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()
diff --git a/docs.html b/docs.html
index 3c1092d..4365627 100755
--- a/docs.html
+++ b/docs.html
@@ -50,6 +50,7 @@ div.contents {
Configuration
Sound Cards
SDR-IQ
+Perseus
Timing
USB Control
Custom Hardware
@@ -80,6 +81,7 @@ complete transceiver. Quisk works with this hardware:
SoftRock connected to the sound card
Many other SDR's connected to the sound card
SDR-IQ connected by USB
+Perseus connected by USB
N2ADR hardware connected by Ethernet and IP
HiQSDR hardware connected by Ethernet and IP
The Hermes-Lite project at hermeslite.com
@@ -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.
+Perseus as Input
+
+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 libperseus-sdr
+ open source library to manage Perseus hardware and receive the I/Q samples stream.
+
+
+
+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:
+
+
+
+make perseus3
+
+
+
+The several sample rates can be selected opening Config panel: in
+the Config tab there is the Samples rates dropdown.
+
+The input analog filter can be switched in using the button Wideband.
+The input attenuator is operate via the button RF, that allows to select
+the four attenuator steps.
+The ADC commands for dithering and preamplifier are found on
+left bottom corner as ADC Dither and ADC Preamp.
+
+
Timing
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.
+
ChangeFrequency(self, tune, vfo, source='', band='', event=None)
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.
+
+Adding Custom Hardware to the Config/Radios Screen
+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:
+
+
+# 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'
+
+
+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.
+
+
Extension Packages
Quisk comes with two extension packages. The freedvpkg package
diff --git a/makefile b/makefile
index 078bf1f..87903ac 100755
--- a/makefile
+++ b/makefile
@@ -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
diff --git a/perseuspkg/README.txt b/perseuspkg/README.txt
new file mode 100755
index 0000000..b6fe841
--- /dev/null
+++ b/perseuspkg/README.txt
@@ -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
+
+
+
diff --git a/perseuspkg/__init__.py b/perseuspkg/__init__.py
new file mode 100755
index 0000000..792d600
--- /dev/null
+++ b/perseuspkg/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/perseuspkg/makefile b/perseuspkg/makefile
new file mode 100755
index 0000000..08b7f11
--- /dev/null
+++ b/perseuspkg/makefile
@@ -0,0 +1,7 @@
+
+perseus2:
+ python2 setup.py build_ext --force --inplace
+
+perseus3:
+ python3 setup.py build_ext --force --inplace
+
diff --git a/perseuspkg/perseus.c b/perseuspkg/perseus.c
new file mode 100755
index 0000000..2bfb534
--- /dev/null
+++ b/perseuspkg/perseus.c
@@ -0,0 +1,498 @@
+/*
+ *
+ * Microtelecom perseus HF receiver
+ *
+ * access module: exposes Python functions needed in quisk_hardware.py
+ * to control hardware
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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", ¶m))
+ 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", ¶m))
+ 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", ¶m))
+ 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", ¶m))
+ 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
+
diff --git a/perseuspkg/quisk_hardware.py b/perseuspkg/quisk_hardware.py
new file mode 100755
index 0000000..aaea0b8
--- /dev/null
+++ b/perseuspkg/quisk_hardware.py
@@ -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")
diff --git a/perseuspkg/quisk_widgets.py b/perseuspkg/quisk_widgets.py
new file mode 100755
index 0000000..e1b4721
--- /dev/null
+++ b/perseuspkg/quisk_widgets.py
@@ -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)
diff --git a/perseuspkg/setup.py b/perseuspkg/setup.py
new file mode 100755
index 0000000..ef48fc7
--- /dev/null
+++ b/perseuspkg/setup.py
@@ -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',
+ ],
+)
+
+
diff --git a/quisk.egg-info/PKG-INFO b/quisk.egg-info/PKG-INFO
index 9163563..909e803 100755
--- a/quisk.egg-info/PKG-INFO
+++ b/quisk.egg-info/PKG-INFO
@@ -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
diff --git a/quisk.egg-info/SOURCES.txt b/quisk.egg-info/SOURCES.txt
index 1b9bbef..c23424b 100755
--- a/quisk.egg-info/SOURCES.txt
+++ b/quisk.egg-info/SOURCES.txt
@@ -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
diff --git a/sdrmicronpkg/quisk_hardware.py b/sdrmicronpkg/quisk_hardware.py
new file mode 100755
index 0000000..7c48db7
--- /dev/null
+++ b/sdrmicronpkg/quisk_hardware.py
@@ -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)
+
+
diff --git a/setup.py b/setup.py
index e5cfc8c..0b8883a 100755
--- a/setup.py
+++ b/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.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']},
diff --git a/soapypkg/build/temp.linux-x86_64-3.6/soapy.o b/soapypkg/build/temp.linux-x86_64-3.6/soapy.o
index 76a600f48e6e789ae25aed06849ef45565abde8c..9a11e0eaaa4e9ecaa6f8cd9158a6615c527516de 100755
GIT binary patch
delta 19757
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
C{AmpU
delta 19413
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