Quisk version 4.2.28

This commit is contained in:
James C. Ahlstrom
2023-12-26 15:39:37 -05:00
commit ffa047c2ef
143 changed files with 52434 additions and 0 deletions
+10
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
View File
@@ -0,0 +1 @@
#
+7
View File
@@ -0,0 +1,7 @@
perseus2:
python2 setup.py build_ext --force --inplace
perseus3:
python3 setup.py build_ext --force --inplace
+505
View File
@@ -0,0 +1,505 @@
/*
*
* 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)
{
if (DEBUG) { 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 {
if (DEBUG) 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 {
if (DEBUG) 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 {
if (DEBUG) 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 {
if (DEBUG) fprintf(stderr, "perseus c: start async input\n");
}
running = 1;
}
}
// Stop sample capture; called from the sound thread.
static void quisk_stop_samples(void)
{
if (DEBUG) { fprintf (stderr, "perseus c: quisk_stop_samples\n"); fflush(stderr); }
// We stop the acquisition...
if (DEBUG) 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)
{
if (DEBUG) 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;
if (DEBUG) { 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();
if (DEBUG) 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
if (DEBUG) 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 {
if (DEBUG) {
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]) {
if (DEBUG) fprintf(stderr, "perseus c: #%d: sample rate: %d\n", i, buf[i]);
i++;
}
}
}
// Configure the receiver for 2 MS/s operations
if (DEBUG) 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);
if (DEBUG) { 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 WB filter:%d\n", param, wb_filter);
freq= param;
if (descr) perseus_set_ddc_center_freq(descr, freq, wb_filter);
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);
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;
if (DEBUG) fprintf (stderr, "perseus c: Set sampling rate %d\n", param);
if (param < 48000) sr = param * 1000;
else sr = param;
if (descr) {
if (running) {
if (DEBUG) 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 {
if (DEBUG) fprintf(stderr, "perseus c: start async input @%d\n", sr);
}
}
} else {
fprintf(stderr, "perseus c: trying 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
+189
View File
@@ -0,0 +1,189 @@
# 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
if DEBUG: 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):
if DEBUG: 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)
if DEBUG: print ("perseus hardware: open")
return txt
def close(self): # Called once to close the Hardware
if DEBUG: print ("perseus hardware: close")
if perseus:
perseus.close_device(1)
def ChangeGain(self, rxtx): # rxtx is '_rx' or '_tx'
if not perseus:
return
if DEBUG: print ("perseus hardware: ChangeGain", rxtx)
pass
def OnButtonRfGain(self, event):
#btn = event.GetEventObject()
n = event.GetEventObject().index
self.att = n * -10
if DEBUG: 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):
if DEBUG: 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
if DEBUG: 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:
if DEBUG: print ("perseus hardware: VarDecimSet: current sampling rate: %d" % self.current_rate)
new_rate = self.current_rate = self.application.vardecim_set
else:
new_rate = self.rates[index]
if DEBUG: print ("perseus hardware: VarDecimSet: New sampling rate: %d" % new_rate)
perseus.set_sampling_rate(int(new_rate))
self.current_rate = int(new_rate)
return int(new_rate)
def VarDecimRange(self): # Return the lowest and highest sample rate.
if DEBUG: print ("perseus hardware: VarDecimRange: %s" % self.rates)
return (self.rates[0], self.rates[-1])
def OnButtonAntenna(self, event):
btn = event.GetEventObject()
n = btn.index
if DEBUG: print ("OnButtonAntenna: %d status: %d" % (n, self.wb))
self.wb = n
perseus.set_input_filter (self.wb)
# 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
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
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',
],
)