Quisk version 4.2.28
This commit is contained in:
@@ -0,0 +1 @@
|
||||
#
|
||||
@@ -0,0 +1,13 @@
|
||||
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
name_of_sound_play = n2adr_sound_usb_play
|
||||
microphone_name = n2adr_sound_usb_mic
|
||||
digital_input_name = ""
|
||||
digital_output_name = ""
|
||||
|
||||
use_rx_udp = 1 # Get ADC samples from UDP
|
||||
rx_udp_ip = "192.168.1.196" # Sample source IP address
|
||||
rx_udp_port = 0xBC77 # Sample source UDP port
|
||||
graph_width = 0.80
|
||||
@@ -0,0 +1,20 @@
|
||||
# This is a second config file that I use to test various hardware configurations.
|
||||
|
||||
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
|
||||
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
name_of_sound_play = n2adr_sound_usb_play
|
||||
name_of_sound_capt = n2adr_sound_pc_capt
|
||||
|
||||
sdriq_name = "/dev/ttyUSB0" # Name of the SDR-IQ device to open
|
||||
|
||||
default_screen = 'WFall'
|
||||
waterfall_y_scale = 80
|
||||
waterfall_y_zero = 40
|
||||
waterfall_graph_y_scale = 40
|
||||
waterfall_graph_y_zero = 90
|
||||
waterfall_graph_size = 160
|
||||
display_fraction = 1.00 # The edges of the full bandwidth are not valid
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic, favorites_file_path
|
||||
|
||||
name_of_sound_play = n2adr_sound_usb_play
|
||||
microphone_name = n2adr_sound_usb_mic
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
rx_udp_clock = 73728000 - 102
|
||||
rx_udp_ip = "192.168.1.213" # Sample source IP address "" for DHCP
|
||||
|
||||
do_repeater_offset = True
|
||||
#bandTransverterOffset = {'10' : 300000}
|
||||
spot_button_keys_tx = True
|
||||
@@ -0,0 +1,5 @@
|
||||
from n2adr import conf3
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
microphone_name = ""
|
||||
@@ -0,0 +1,40 @@
|
||||
# This is a config file to test the microphone by sending microphone Tx playback to the audio out.
|
||||
# Set the frequency to zero, and press FDX and PTT.
|
||||
# Set these values for DEBUG_MIC in sound.c:
|
||||
# 0: Normal FFT.
|
||||
# 1: Send filtered Tx audio to the FFT.
|
||||
# 2: Send mic playback to the FFT.
|
||||
# 3: Send unfiltered mono mic audio to the FFT.
|
||||
|
||||
import sys
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
import _quisk as QS
|
||||
|
||||
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
|
||||
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
|
||||
from n2adr.quisk_conf import mixer_settings
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
name_of_sound_capt = n2adr_sound_pc_capt
|
||||
name_of_sound_play = ''
|
||||
microphone_name = n2adr_sound_usb_mic
|
||||
name_of_mic_play = n2adr_sound_usb_play
|
||||
|
||||
graph_y_scale = 160
|
||||
|
||||
mic_sample_rate = 48000
|
||||
|
||||
sample_rate = 48000
|
||||
mic_playback_rate = 48000
|
||||
mic_out_volume = 0.6
|
||||
add_fdx_button = 1
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
def OnButtonPTT(self, event):
|
||||
if event.GetEventObject().GetValue():
|
||||
QS.set_key_down(1)
|
||||
else:
|
||||
QS.set_key_down(0)
|
||||
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
#hamlib_port = 4575 # Standard port for Quisk control. Set the port in Hamlib to 4575 too.
|
||||
hamlib_port = 4532 # Default port for rig 2. Use this if you can not set the Hamlib port.
|
||||
if sys.platform == "win32":
|
||||
pass
|
||||
elif 0:
|
||||
digital_input_name = 'pulse'
|
||||
digital_output_name =''
|
||||
else:
|
||||
digital_input_name = 'hw:Loopback,0'
|
||||
digital_output_name = digital_input_name
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# This is a second config file to test the softrock radios.
|
||||
|
||||
from n2adr.quisk_conf import n2adr_sound_pc_capt, n2adr_sound_pc_play, n2adr_sound_usb_play, n2adr_sound_usb_mic
|
||||
from n2adr.quisk_conf import latency_millisecs, data_poll_usec, favorites_file_path
|
||||
from n2adr.quisk_conf import mixer_settings
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
name_of_sound_capt = n2adr_sound_pc_capt
|
||||
name_of_sound_play = n2adr_sound_usb_play
|
||||
|
||||
default_screen = 'WFall'
|
||||
waterfall_y_scale = 80
|
||||
waterfall_y_zero = 40
|
||||
waterfall_graph_y_scale = 40
|
||||
waterfall_graph_y_zero = 90
|
||||
waterfall_graph_size = 160
|
||||
display_fraction = 1.00
|
||||
|
||||
sample_rate = 48000
|
||||
playback_rate = 48000
|
||||
|
||||
do_repeater_offset = True
|
||||
#bandTransverterOffset = {'40' : 300000}
|
||||
|
||||
# Microphone capture and playback:
|
||||
microphone_name = n2adr_sound_usb_mic
|
||||
name_of_mic_play = n2adr_sound_pc_play
|
||||
mic_playback_rate = sample_rate
|
||||
mic_out_volume = 0.6
|
||||
@@ -0,0 +1,14 @@
|
||||
import sys
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
if sys.platform == "win32":
|
||||
digital_output_name = 'CABLE-A Input'
|
||||
elif 0:
|
||||
digital_input_name = 'pulse'
|
||||
digital_output_name =''
|
||||
else:
|
||||
digital_output_name = 'hw:Loopback,0'
|
||||
|
||||
name_of_sound_play = ''
|
||||
microphone_name = ''
|
||||
@@ -0,0 +1,72 @@
|
||||
# This is the hardware control file for my shack.
|
||||
# It is for the Hermes-Lite2 5 watt output which uses only the antenna tuner.
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
from hermes.quisk_hardware import Hardware as BaseHw
|
||||
from n2adr import station_hardware
|
||||
|
||||
class Hardware(BaseHw):
|
||||
def __init__(self, app, conf):
|
||||
BaseHw.__init__(self, app, conf)
|
||||
self.GUI = None
|
||||
self.vfo_frequency = 0 # current vfo frequency
|
||||
# Other hardware
|
||||
self.anttuner = station_hardware.AntennaTuner(app, conf) # Control the antenna tuner
|
||||
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
|
||||
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
|
||||
def open(self):
|
||||
if False:
|
||||
from n2adr.station_hardware import StationControlGUI
|
||||
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
|
||||
self.GUI.Show()
|
||||
self.anttuner.open()
|
||||
return BaseHw.open(self)
|
||||
def close(self):
|
||||
self.anttuner.close()
|
||||
self.controlbox.close()
|
||||
return BaseHw.close(self)
|
||||
def ChangeFilterFrequency(self, tx_freq):
|
||||
if tx_freq and tx_freq > 0:
|
||||
if self.GUI:
|
||||
self.GUI.SetTxFreq(tx_freq)
|
||||
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
|
||||
else:
|
||||
self.anttuner.SetTxFreq(tx_freq)
|
||||
self.v2filter.SetTxFreq(tx_freq)
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.ChangeFilterFrequency(tx_freq)
|
||||
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
|
||||
def ChangeBand(self, band):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
ret = BaseHw.ChangeBand(self, band)
|
||||
self.anttuner.ChangeBand(band)
|
||||
#self.lpfilter.ChangeBand(band)
|
||||
#self.hpfilter.ChangeBand(band)
|
||||
self.v2filter.ChangeBand(band)
|
||||
self.CorrectSmeter()
|
||||
return ret
|
||||
def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
self.anttuner.HeartBeat()
|
||||
self.v2filter.HeartBeat()
|
||||
self.controlbox.HeartBeat()
|
||||
return BaseHw.HeartBeat(self)
|
||||
def OnSpot(self, level):
|
||||
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
||||
self.anttuner.OnSpot(level)
|
||||
return BaseHw.OnSpot(self, level)
|
||||
def OnButtonRfGain(self, event):
|
||||
self.v2filter.OnButtonRfGain(event)
|
||||
self.CorrectSmeter()
|
||||
def CorrectSmeter(self): # S-meter correction can change with band or RF gain
|
||||
return
|
||||
if self.band == '40': # Basic S-meter correction by band
|
||||
self.correct_smeter = 20.5
|
||||
else:
|
||||
self.correct_smeter = 20.5
|
||||
#self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain
|
||||
#self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant
|
||||
def OnButtonPTT(self, event):
|
||||
self.controlbox.OnButtonPTT(event)
|
||||
return BaseHw.OnButtonPTT(self, event)
|
||||
@@ -0,0 +1,47 @@
|
||||
# This is the config file from my shack, which controls various hardware.
|
||||
# The files to control my 2010 transceiver and for the improved version HiQSDR
|
||||
# are in the package directory HiQSDR.
|
||||
|
||||
import sys
|
||||
|
||||
sample_rate = 48000
|
||||
|
||||
if sys.platform == 'win32':
|
||||
favorites_file_path = "C:/pub/quisk_favorites.txt"
|
||||
settings_file_path = "C:/pub/quisk_settings.json"
|
||||
name_of_sound_play = ""
|
||||
name_of_sound_capt = "Primary"
|
||||
microphone_name = ""
|
||||
name_of_mic_play = ""
|
||||
else:
|
||||
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
|
||||
settings_file_path = "/home/jim/pub/quisk_settings.json"
|
||||
name_of_sound_play = ""
|
||||
name_of_sound_capt = "hw:0"
|
||||
microphone_name = ""
|
||||
name_of_mic_play = ""
|
||||
#lin_microphone_name = "ALC1150 Analog"
|
||||
#lin_name_of_mic_play = "USB Sound Device"
|
||||
|
||||
# These are for CM106 like sound device
|
||||
#n2adr_sound_usb_mic = 'alsa:USB Sound Device'
|
||||
#microphone_name = n2adr_sound_usb_mic
|
||||
#mixer_settings = [
|
||||
# (microphone_name, 16, 1), # PCM capture from line
|
||||
# (microphone_name, 14, 0), # PCM capture switch
|
||||
# (microphone_name, 11, 1), # line capture switch
|
||||
# (microphone_name, 12, 0.70), # line capture volume
|
||||
# (microphone_name, 3, 0), # mic playback switch
|
||||
# (microphone_name, 9, 0), # mic capture switch
|
||||
# ]
|
||||
|
||||
# These are for Asus internal sound
|
||||
n2adr_sound_usb_mic = 'alsa:ALC1150 Analog'
|
||||
microphone_name = n2adr_sound_usb_mic
|
||||
mixer_settings = [
|
||||
(microphone_name, 19, 2), # PCM capture from line
|
||||
(microphone_name, 24, 0), # PCM capture switch
|
||||
(microphone_name, 22, 1), # line capture switch
|
||||
(microphone_name, 27, 0), # line capture volume boost
|
||||
(microphone_name, 21, 0.70), # line capture volume
|
||||
]
|
||||
@@ -0,0 +1,202 @@
|
||||
# These are the configuration parameters for receiving the
|
||||
# 10.7 MHz IF output of the AOR AR8600 receiver with my
|
||||
# transceiver. This results in a 100 kHz to 3 GHz
|
||||
# wide range receiver with pan adapter.
|
||||
#
|
||||
# Due to noise starting at 11.18 MHz when tuned to 449.0 MHz, we tune to 10.5 MHz center.
|
||||
#
|
||||
# Note: The AR8600 IF output in WFM mode seems to tune in 10kHz increments
|
||||
# no matter what the step size, even though the display reads a
|
||||
# different frequency.
|
||||
|
||||
# The AR8600 inverts the spectrum of these bands: 10, 2, 220, 440, 900
|
||||
# The AR8600 does not invert these bands: 1240
|
||||
# The change from inverted to non-inverted is about 1040 MHz.
|
||||
|
||||
# Please do not change this sample file.
|
||||
# Instead copy it to your own .quisk_conf.py and make changes there.
|
||||
# See quisk_conf_defaults.py for more information.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import time, traceback, os
|
||||
import _quisk as QS
|
||||
import serial # From the pyserial package
|
||||
|
||||
from n2adr.quisk_conf import *
|
||||
from n2adr import scanner_widgets as quisk_widgets
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
bandLabels = [ ('60',) * 5, '40', '20',
|
||||
'15', '12', '10', '2', '220', '440', '900', '1240', ('Time',) * len(bandTime)]
|
||||
|
||||
# Define the Hardware class in this config file instead of a separate file.
|
||||
|
||||
from hiqsdr.quisk_hardware import Hardware as BaseHardware
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
self.ar8600_frequency = 0 # current AR8600 tuning frequency
|
||||
self.hware_frequency = 0 # current hardware VFO frequency
|
||||
self.vfo_frequency = 0 # current Quisk VFO frequency
|
||||
self.invert = 1 # The frequency spectrum is backwards
|
||||
self.serial = None # the open serial port
|
||||
self.timer = 0.02 # time between AR8600 commands in seconds
|
||||
self.time0 = 0 # time of last AR8600 command
|
||||
self.serial_out = [] # send commands slowly
|
||||
self.offset = 10700000 # frequency offset from AR8600 tuning freq to IF output
|
||||
self.tx_freq = 0 # current frequency
|
||||
conf.BandEdge['220'] = (222000000, 225000000)
|
||||
conf.BandEdge['440'] = (420000000, 450000000)
|
||||
conf.BandEdge['900'] = (902000000, 928000000)
|
||||
conf.BandEdge['1240'] = (1240000000, 1300000000)
|
||||
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
|
||||
rpt_file = os.path.join(rpt_file, 'MetroCor.txt')
|
||||
fp = open(rpt_file, 'r')
|
||||
self.repeaters = {}
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line and line[0] != '#':
|
||||
line = line.split('\t')
|
||||
fout = int(float(line[0]) * 1000000 + 0.1)
|
||||
text = "%s %s, %s" % (line[2], line[3], line[5])
|
||||
if fout in self.repeaters:
|
||||
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
|
||||
else:
|
||||
self.repeaters[fout] = text
|
||||
fp.close()
|
||||
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
|
||||
rpt_file = os.path.join(rpt_file, 'ARCC.csv')
|
||||
fp = open(rpt_file, 'r')
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line and line[0] != '#':
|
||||
line = line.split(',')
|
||||
fout = float(line[3])
|
||||
if fout >= 2000.0:
|
||||
continue
|
||||
fout = int(fout * 1000000 + 0.1)
|
||||
text = "%s %s, %s" % (line[5], line[2], line[0])
|
||||
if fout in self.repeaters:
|
||||
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
|
||||
else:
|
||||
self.repeaters[fout] = text
|
||||
fp.close()
|
||||
rpt_file = os.path.normpath(os.path.join(os.getcwd(), '..'))
|
||||
rpt_file = os.path.join(rpt_file, 'Repeaters.csv')
|
||||
fp = open(rpt_file, 'r')
|
||||
for line in fp:
|
||||
line = line.strip()
|
||||
if line and line[0] != '#':
|
||||
line = line.split(',')
|
||||
fout = float(line[3])
|
||||
if fout >= 2000.0:
|
||||
continue
|
||||
fout = int(fout * 1000000 + 0.1)
|
||||
if line[0]:
|
||||
text = "%s %s, %s" % (line[5], line[2], line[0])
|
||||
else:
|
||||
text = line[5]
|
||||
if fout in self.repeaters:
|
||||
self.repeaters[fout] = "%s ; %s" % (self.repeaters[fout], text)
|
||||
else:
|
||||
self.repeaters[fout] = text
|
||||
fp.close()
|
||||
for freq, text in list(self.repeaters.items()):
|
||||
if len(text) > 80:
|
||||
t =''
|
||||
stations = text.split(';')
|
||||
for s in stations:
|
||||
s = s.strip()
|
||||
t = t + s.split()[0] + ' ' + s.split(',')[1] + '; '
|
||||
self.repeaters[freq] = t
|
||||
self.rpt_freq_list = list(self.repeaters)
|
||||
self.rpt_freq_list.sort()
|
||||
def OpenPort(self):
|
||||
if sys.platform == "win32":
|
||||
tty_list = ("COM7", "COM8")
|
||||
else:
|
||||
tty_list = ("/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2")
|
||||
for tty_name in tty_list:
|
||||
try:
|
||||
port = serial.Serial(port=tty_name, baudrate=9600,
|
||||
stopbits=serial.STOPBITS_TWO, xonxoff=1, timeout=0)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
for i in range(3):
|
||||
port.write('VR\r')
|
||||
time.sleep(0.1)
|
||||
chars = port.read(1024)
|
||||
if "VR0101" in chars:
|
||||
self.serial = port
|
||||
port.write('MD0\r') # set WFM mode so the IF output is available
|
||||
break
|
||||
if self.serial:
|
||||
break
|
||||
else:
|
||||
port.close()
|
||||
def open(self):
|
||||
self.OpenPort()
|
||||
QS.invert_spectrum(self.invert)
|
||||
t = BaseHardware.open(self) # save the message
|
||||
return t
|
||||
def close(self):
|
||||
BaseHardware.close(self)
|
||||
if self.serial:
|
||||
self.serial.write('EX\r')
|
||||
time.sleep(1) # wait for output to drain, but don't block
|
||||
self.serial.close()
|
||||
self.serial = None
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.tx_freq = tx_freq
|
||||
try:
|
||||
rpt = self.repeaters[tx_freq]
|
||||
except KeyError:
|
||||
self.application.bottom_widgets.UpdateText('')
|
||||
else:
|
||||
self.application.bottom_widgets.UpdateText(rpt)
|
||||
if vfo_freq != self.vfo_frequency and vfo_freq >= 10000:
|
||||
self.vfo_frequency = vfo_freq
|
||||
# Calculate new AR8600 and hardware frequencies
|
||||
ar8600 = (vfo_freq + 50000) // 100000 * 100000 - 200000
|
||||
if self.ar8600_frequency != ar8600:
|
||||
self.ar8600_frequency = ar8600
|
||||
self.SendAR8600('RF%010d\r' % ar8600)
|
||||
if ar8600 < 1040000000:
|
||||
self.invert = 1
|
||||
else:
|
||||
self.invert = 0
|
||||
QS.invert_spectrum(self.invert)
|
||||
if self.invert:
|
||||
hware = self.offset - vfo_freq + self.ar8600_frequency
|
||||
else:
|
||||
hware = self.offset + vfo_freq - self.ar8600_frequency
|
||||
if self.hware_frequency != hware:
|
||||
self.hware_frequency = hware
|
||||
BaseHardware.ChangeFrequency(self, 0, hware)
|
||||
#print 'AR8600 Hware', self.ar8600_frequency, self.hware_frequency
|
||||
return tx_freq, vfo_freq
|
||||
def SendAR8600(self, msg): # Send commands to the AR8600, but not too fast
|
||||
if self.serial:
|
||||
if time.time() - self.time0 > self.timer:
|
||||
self.serial.write(msg) # send message now
|
||||
self.time0 = time.time()
|
||||
else:
|
||||
self.serial_out.append(msg) # send message later
|
||||
def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
BaseHardware.HeartBeat(self)
|
||||
if self.serial:
|
||||
chars = self.serial.read(1024)
|
||||
#if chars:
|
||||
# print chars
|
||||
if self.serial_out and time.time() - self.time0 > self.timer:
|
||||
self.serial.write(self.serial_out[0])
|
||||
self.time0 = time.time()
|
||||
del self.serial_out[0]
|
||||
@@ -0,0 +1,80 @@
|
||||
# This is the hardware file from my shack, which controls various hardware.
|
||||
# The files to control my 2010 transceiver and for the improved version HiQSDR
|
||||
# are in the package directory HiQSDR.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
from hiqsdr.quisk_hardware import Hardware as BaseHw
|
||||
from n2adr import station_hardware
|
||||
|
||||
class Hardware(BaseHw):
|
||||
def __init__(self, app, conf):
|
||||
BaseHw.__init__(self, app, conf)
|
||||
self.GUI = None
|
||||
self.vfo_frequency = 0 # current vfo frequency
|
||||
self.rf_gain_labels = ('RF 0 dB', 'RF +16')
|
||||
self.rf_gain = 0 # Preamp or attenuation in dB; changed via app.Hardware
|
||||
# Other hardware
|
||||
self.anttuner = station_hardware.AntennaTuner(app, conf) # Control the antenna tuner
|
||||
#self.lpfilter = station_hardware.LowPassFilter(app, conf) # Control LP filter box
|
||||
#self.hpfilter = station_hardware.HighPassFilter(app, conf) # Control HP filter box
|
||||
self.controlbox = station_hardware.ControlBox(app, conf) # Control my Station Control Box
|
||||
self.v2filter = station_hardware.FilterBoxV2(app, conf) # Control V2 filter box
|
||||
def open(self):
|
||||
if False:
|
||||
from n2adr.station_hardware import StationControlGUI
|
||||
self.GUI = StationControlGUI(self.application.main_frame, self, self.application, self.conf)
|
||||
self.GUI.Show()
|
||||
self.anttuner.open()
|
||||
return BaseHw.open(self)
|
||||
def close(self):
|
||||
self.anttuner.close()
|
||||
self.controlbox.close()
|
||||
return BaseHw.close(self)
|
||||
def ChangeFilterFrequency(self, tx_freq):
|
||||
if tx_freq and tx_freq > 0:
|
||||
if self.GUI:
|
||||
self.GUI.SetTxFreq(tx_freq)
|
||||
self.GUI.freq_entry.ChangeValue("%.3f" % (tx_freq * 1E-6))
|
||||
else:
|
||||
self.anttuner.SetTxFreq(tx_freq)
|
||||
self.v2filter.SetTxFreq(tx_freq)
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.ChangeFilterFrequency(tx_freq)
|
||||
return BaseHw.ChangeFrequency(self, tx_freq, vfo_freq, source, band, event)
|
||||
def ChangeBand(self, band):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
ret = BaseHw.ChangeBand(self, band)
|
||||
self.anttuner.ChangeBand(band)
|
||||
#self.lpfilter.ChangeBand(band)
|
||||
#self.hpfilter.ChangeBand(band)
|
||||
self.v2filter.ChangeBand(band)
|
||||
self.CorrectSmeter()
|
||||
return ret
|
||||
def HeartBeat(self): # Called at about 10 Hz by the main
|
||||
self.anttuner.HeartBeat()
|
||||
#self.lpfilter.HeartBeat()
|
||||
#self.hpfilter.HeartBeat()
|
||||
self.v2filter.HeartBeat()
|
||||
self.controlbox.HeartBeat()
|
||||
return BaseHw.HeartBeat(self)
|
||||
def OnSpot(self, level):
|
||||
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
||||
self.anttuner.OnSpot(level)
|
||||
return BaseHw.OnSpot(self, level)
|
||||
def OnButtonRfGain(self, event):
|
||||
#self.hpfilter.OnButtonRfGain(event)
|
||||
self.v2filter.OnButtonRfGain(event)
|
||||
self.CorrectSmeter()
|
||||
def CorrectSmeter(self): # S-meter correction can change with band or RF gain
|
||||
if self.band == '40': # Basic S-meter correction by band
|
||||
self.correct_smeter = 20.5
|
||||
else:
|
||||
self.correct_smeter = 20.5
|
||||
self.correct_smeter -= self.rf_gain / 6.0 # Correct S-meter for RF gain
|
||||
self.application.waterfall.ChangeRfGain(self.rf_gain) # Waterfall colors are constant
|
||||
def OnButtonPTT(self, event):
|
||||
self.controlbox.OnButtonPTT(event)
|
||||
return BaseHw.OnButtonPTT(self, event)
|
||||
@@ -0,0 +1,56 @@
|
||||
# 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 wx, time
|
||||
import _quisk as QS
|
||||
|
||||
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
|
||||
#start_row = app.widget_row # The first available row
|
||||
#start_col = app.button_start_col # The start of the button columns
|
||||
#b = app.QuiskCycleCheckbutton(frame, self.OnAntTuner, ('Antenna', 'Ant 0', 'Ant 1'))
|
||||
#bw, bh = b.GetMinSize()
|
||||
#gbs.Add(b, (start_row, start_col), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L+')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 2), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'L-')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 4), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C+')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 6), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'C-')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 8), (1, 2), flag=wx.EXPAND)
|
||||
#b = app.QuiskPushbutton(frame, self.OnAntTuner, 'Save')
|
||||
#b.Enable(0)
|
||||
#gbs.Add(b, (start_row, start_col + 10), (1, 2), flag=wx.EXPAND)
|
||||
#self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
|
||||
#gbs.Add(self.swr_label, (start_row, start_col + 2), (1, 10), flag=wx.EXPAND)
|
||||
#b = app.QuiskCheckbutton(frame, None, text='')
|
||||
#gbs.Add(b, (start_row, start_col + 25), (1, 2), flag=wx.EXPAND)
|
||||
# Example of a horizontal slider:
|
||||
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
|
||||
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
|
||||
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
|
||||
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
|
||||
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
|
||||
# def OnPreamp(self, event):
|
||||
# print event.GetPosition()
|
||||
self.num_rows_added = 0
|
||||
#def OnAntTuner(self, event):
|
||||
# btn = event.GetEventObject()
|
||||
# text = btn.GetLabel()
|
||||
# self.hardware.OnAntTuner(text)
|
||||
#def UpdateText(self, text):
|
||||
# self.swr_label.SetLabel(text)
|
||||
@@ -0,0 +1,140 @@
|
||||
# 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 wx, time
|
||||
import _quisk as QS
|
||||
|
||||
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
|
||||
row = 4 # The next available row
|
||||
b = app.QuiskPushbutton(frame, None, 'Tune')
|
||||
bw, bh = b.GetMinSize()
|
||||
b.Enable(0)
|
||||
gbs.Add(b, (row, 0), (1, 2), flag=wx.EXPAND)
|
||||
b = app.QuiskPushbutton(frame, None, '')
|
||||
gbs.Add(b, (row, 2), (1, 2), flag=wx.EXPAND)
|
||||
b = app.QuiskPushbutton(frame, None, '')
|
||||
gbs.Add(b, (row, 4), (1, 2), flag=wx.EXPAND)
|
||||
b = self.btnScanner = app.QuiskCheckbutton(frame, self.OnBtnScanner, text='Scanner', use_right=True)
|
||||
self.scan_timer = wx.Timer(b) # timed events for the scanner
|
||||
b.Bind(wx.EVT_TIMER, self.OnTimerEvent)
|
||||
gbs.Add(b, (row, 6), (1, 2), flag=wx.EXPAND)
|
||||
b = self.btnNext = app.QuiskPushbutton(frame, self.OnBtnNext, 'Next', True)
|
||||
gbs.Add(b, (row, 8), (1, 2), flag=wx.EXPAND)
|
||||
b = app.QuiskCheckbutton(frame, self.OnBtnRptr, text='Rptr')
|
||||
b.SetValue(True, True)
|
||||
gbs.Add(b, (row, 10), (1, 2), flag=wx.EXPAND)
|
||||
self.swr_label = app.QuiskText(frame, 'Watts 000 SWR 10.1 Zh Ind 22 Cap 33 Freq 28100 (7777)', bh)
|
||||
gbs.Add(self.swr_label, (row, 15), (1, 12), flag=wx.EXPAND)
|
||||
# Example of a horizontal slider:
|
||||
# lab = wx.StaticText(frame, -1, 'Preamp', style=wx.ALIGN_CENTER)
|
||||
# gbs.Add(lab, (5,0), flag=wx.EXPAND)
|
||||
# sl = wx.Slider(frame, -1, 1024, 0, 2048) # parent, -1, initial, min, max
|
||||
# gbs.Add(sl, (5,1), (1, 5), flag=wx.EXPAND)
|
||||
# sl.Bind(wx.EVT_SCROLL, self.OnPreamp)
|
||||
# def OnPreamp(self, event):
|
||||
# print event.GetPosition()
|
||||
def UpdateText(self, text):
|
||||
self.swr_label.SetLabel(text)
|
||||
def OnBtnRptr(self, event):
|
||||
btn = event.GetEventObject()
|
||||
if btn.GetValue():
|
||||
self.config.freq_spacing = 5000
|
||||
else:
|
||||
self.config.freq_spacing = 0
|
||||
def OnBtnNext(self, event):
|
||||
self.direction = self.btnNext.direction # +1 for left -> go up; -1 for down
|
||||
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
|
||||
self.scanner = False
|
||||
if self.keep_going:
|
||||
if not self.ScanScreen(event):
|
||||
self.MoveVfo(event)
|
||||
self.scan_timer.Start(500)
|
||||
else:
|
||||
self.ScanScreen(event)
|
||||
def ScanScreen(self, event): # Look for signals on the current screen
|
||||
lst = self.hardware.rpt_freq_list
|
||||
app = self.application
|
||||
vfo = app.VFO
|
||||
tx_freq = vfo + app.txFreq
|
||||
sample_rate = app.sample_rate
|
||||
limit = int(sample_rate / 2.0 * self.config.display_fraction * 0.95) # edge of screen
|
||||
self.scan_n1 = None
|
||||
self.scan_n = None
|
||||
for n in range(len(lst)):
|
||||
if lst[n] > vfo - limit and self.scan_n1 is None:
|
||||
self.scan_n1 = n # inclusive
|
||||
if lst[n] >= tx_freq and self.scan_n is None:
|
||||
self.scan_n = n
|
||||
if lst[n] > vfo + limit:
|
||||
break
|
||||
self.scan_n2 = n # inclusive
|
||||
if self.scan_n is None:
|
||||
self.scan_n = self.scan_n1
|
||||
if self.direction > 0: # left click; go up
|
||||
seq = list(range(self.scan_n + 1, self.scan_n2 + 1))
|
||||
if not self.keep_going:
|
||||
seq += list(range(self.scan_n1, self.scan_n))
|
||||
else: # right click; go down
|
||||
seq = list(range(self.scan_n - 1, self.scan_n1 - 1, -1))
|
||||
if not self.keep_going:
|
||||
seq += list(range(self.scan_n2, self.scan_n, -1))
|
||||
for n in seq:
|
||||
freq = lst[n]
|
||||
if not QS.get_squelch(freq - vfo):
|
||||
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
|
||||
return True # frequency was changed
|
||||
return False # frequency was not changed
|
||||
def MoveVfo(self, event): # Move the VFO to look for further signals
|
||||
lst = self.hardware.rpt_freq_list
|
||||
app = self.application
|
||||
vfo = app.VFO
|
||||
tx_freq = vfo + app.txFreq
|
||||
sample_rate = app.sample_rate
|
||||
if self.direction > 0: # left click; go up
|
||||
n = self.scan_n2 + 1
|
||||
if n >= len(lst):
|
||||
n = 0
|
||||
freq = lst[n]
|
||||
vfo = freq + sample_rate * 4 // 10
|
||||
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
|
||||
else: # right click; go down
|
||||
n = self.scan_n1 - 1
|
||||
if n < 0:
|
||||
n = len(lst) - 1
|
||||
freq = lst[n]
|
||||
vfo = freq - sample_rate * 4 // 10
|
||||
app.ChangeHwFrequency(freq - vfo, vfo, 'Repeater', event)
|
||||
def OnBtnScanner(self, event):
|
||||
self.direction = self.btnScanner.direction # +1 for left -> go up; -1 for down
|
||||
self.keep_going = wx.GetKeyState(wx.WXK_SHIFT) # if Shift is down, move to next band
|
||||
self.scanner = True
|
||||
if self.btnScanner.GetValue():
|
||||
self.btnNext.Enable(0)
|
||||
if self.keep_going:
|
||||
if not self.ScanScreen(event):
|
||||
self.MoveVfo(event)
|
||||
else:
|
||||
self.ScanScreen(event)
|
||||
self.scan_timer.Start(500)
|
||||
else:
|
||||
self.btnNext.Enable(1)
|
||||
self.scan_timer.Stop()
|
||||
def OnTimerEvent(self, event):
|
||||
if QS.get_squelch(self.application.txFreq):
|
||||
if self.keep_going:
|
||||
if not self.ScanScreen(event):
|
||||
self.MoveVfo(event)
|
||||
else:
|
||||
self.ScanScreen(event)
|
||||
elif not self.scanner:
|
||||
self.scan_timer.Stop()
|
||||
@@ -0,0 +1,92 @@
|
||||
#! /usr/bin/python
|
||||
|
||||
# All QUISK software is Copyright (C) 2006-2011 by James C. Ahlstrom.
|
||||
# This free software is licensed for use under the GNU General Public
|
||||
# License (GPL), see http://www.opensource.org.
|
||||
# Note that there is NO WARRANTY AT ALL. USE AT YOUR OWN RISK!!
|
||||
|
||||
"Select the desired hardware, and start Quisk"
|
||||
|
||||
import sys, wx, subprocess, os
|
||||
|
||||
Choices = [
|
||||
(' My Transceiver', 'n2adr/quisk_conf.py', ''),
|
||||
(' VHF/UHF Receiver', 'n2adr/uhfrx_conf.py', ''),
|
||||
(' Softrock Rx Ensemble', 'softrock/conf_rx_ensemble2.py', 'n2adr/conf2.py'),
|
||||
(' Softrock Rx/Tx Ensemble', 'softrock/conf_rx_tx_ensemble.py', 'n2adr/conf6.py'),
|
||||
(' Plain Sound Card, Rx only', 'n2adr/conf2.py', ''),
|
||||
(' Test microphone sound', 'n2adr/conf4.py', ''),
|
||||
(' SDR-IQ, receive only, antenna to RF input', 'quisk_conf_sdriq.py', 'n2adr/conf2.py'),
|
||||
(' AOR AR8600 with IF to my hardware', 'n2adr/quisk_conf_8600.py', ''),
|
||||
(' AOR AR8600 with IF to SDR-IQ', 'quisk_conf_sdr8600.py', 'n2adr/conf2.py'),
|
||||
(' Fldigi with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf5.py'),
|
||||
(' Freedv.org Rx with my transceiver', 'n2adr/quisk_conf.py', 'n2adr/conf7.py'),
|
||||
(' Hermes-Lite', 'hermes/quisk_conf.py', 'n2adr/conf3.py'),
|
||||
(' Odyssey', 'odyssey/quisk_conf.py', 'n2adr/conf1.py'),
|
||||
(' My Transceiver to Hermes-Lite', 'Quisk2Hermes', ''),
|
||||
]
|
||||
|
||||
if sys.platform == 'win32':
|
||||
os.chdir('C:\\pub\\quisk')
|
||||
exe = "C:\\python27\\pythonw.exe"
|
||||
else:
|
||||
os.chdir('/home/jim/pub/quisk')
|
||||
exe = "/usr/bin/python"
|
||||
|
||||
class ListBoxFrame(wx.Frame):
|
||||
def __init__(self):
|
||||
wx.Frame.__init__(self, None, -1, 'Select Hardware')
|
||||
font = wx.Font(14, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL)
|
||||
self.SetFont(font)
|
||||
charx = self.GetCharWidth()
|
||||
chary = self.GetCharHeight()
|
||||
width = 0
|
||||
height = chary * 2
|
||||
tlist = []
|
||||
for txt, conf1, conf2 in Choices:
|
||||
text = "%s, %s" % (txt, conf1)
|
||||
if conf2:
|
||||
text = "%s, %s" % (text, conf2)
|
||||
tlist.append(text)
|
||||
w, h = self.GetTextExtent(text)
|
||||
width = max(width, w)
|
||||
height += h
|
||||
width += 3 * chary
|
||||
lb = wx.ListBox(self, -1, (0, 0), (width, height), tlist, wx.LB_SINGLE)
|
||||
lb.SetSelection(0)
|
||||
lb.SetFont(font)
|
||||
lb.Bind(wx.EVT_LISTBOX_DCLICK, self.OnDClick, lb)
|
||||
lb.Bind(wx.EVT_KEY_DOWN, self.OnChar)
|
||||
self.SetClientSize((width, height))
|
||||
def OnDClick(self, event):
|
||||
lb = event.GetEventObject()
|
||||
index = lb.GetSelection()
|
||||
text, conf1, conf2 = Choices[index]
|
||||
if conf1 == "Quisk2Hermes":
|
||||
subprocess.Popen([exe, 'quisk.py', '-c', 'n2adr/quisk_conf.py', '--local', 'Q2H'])
|
||||
subprocess.Popen([exe, 'quisk.py', '-c', 'hermes/quisk_conf.py', '--config2', 'n2adr/conf3A.py', '--local', 'Q2H'])
|
||||
else:
|
||||
cmd = [exe, 'quisk.py', '-c', conf1]
|
||||
if conf2:
|
||||
cmd = cmd + ['--config2', conf2]
|
||||
subprocess.Popen(cmd)
|
||||
self.Destroy()
|
||||
def OnChar(self, event):
|
||||
if event.GetKeyCode() == 13:
|
||||
self.OnDClick(event)
|
||||
else:
|
||||
event.Skip()
|
||||
|
||||
class App(wx.App):
|
||||
def __init__(self):
|
||||
if sys.stdout.isatty():
|
||||
wx.App.__init__(self, redirect=False)
|
||||
else:
|
||||
wx.App.__init__(self, redirect=True)
|
||||
def OnInit(self):
|
||||
frame = ListBoxFrame()
|
||||
frame.Show()
|
||||
return True
|
||||
|
||||
app = App()
|
||||
app.MainLoop()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,65 @@
|
||||
# This is the config file for the VHF/UHF receiver and transmitter.
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys, struct, socket, traceback
|
||||
|
||||
settings_file_path = "../quisk_settings.json"
|
||||
|
||||
DEBUG = 0
|
||||
if sys.platform == "win32":
|
||||
n2adr_sound_pc_capt = 'Line In (Realtek High Definition Audio)'
|
||||
n2adr_sound_pc_play = 'Speakers (Realtek High Definition Audio)'
|
||||
n2adr_sound_usb_play = 'Primary'
|
||||
n2adr_sound_usb_mic = 'Primary'
|
||||
latency_millisecs = 150
|
||||
data_poll_usec = 20000
|
||||
favorites_file_path = "C:/pub/quisk_favorites.txt"
|
||||
elif 0: # portaudio devices
|
||||
name_of_sound_play = 'portaudio:CODEC USB'
|
||||
microphone_name = "portaudio:AK5370"
|
||||
latency_millisecs = 150
|
||||
data_poll_usec = 5000
|
||||
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
|
||||
else: # alsa devices
|
||||
n2adr_sound_pc_capt = 'alsa:ALC1150 Analog'
|
||||
n2adr_sound_pc_play = 'alsa:ALC1150 Analog'
|
||||
n2adr_sound_usb_play = 'alsa:USB Sound Device'
|
||||
n2adr_sound_usb_mic = 'alsa:USB Sound Device'
|
||||
latency_millisecs = 150
|
||||
data_poll_usec = 5000
|
||||
favorites_file_path = "/home/jim/pub/quisk_favorites.txt"
|
||||
|
||||
name_of_sound_capt = ""
|
||||
name_of_sound_play = n2adr_sound_pc_play
|
||||
microphone_name = n2adr_sound_pc_capt
|
||||
|
||||
playback_rate = 48000
|
||||
agc_off_gain = 80
|
||||
do_repeater_offset = True
|
||||
|
||||
station_display_lines = 1
|
||||
# DX cluster telent login data, thanks to DJ4CM.
|
||||
dxClHost = ''
|
||||
#dxClHost = 'dxc.w8wts.net'
|
||||
dxClPort = 7373
|
||||
user_call_sign = 'n2adr'
|
||||
|
||||
bandLabels = ['6', '2', '1.25', '70cm', '33cm', '23cm', 'WWV']
|
||||
bandState['WWV'] = (19990000, 10000, 'AM')
|
||||
BandEdge['WWV'] = (19500000, 20500000)
|
||||
|
||||
use_rx_udp = 17 # Get ADC samples from UDP
|
||||
rx_udp_ip = "192.168.1.199" # Sample source IP address
|
||||
rx_udp_port = 0xAA53 # Sample source UDP port
|
||||
#rx_clk38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
|
||||
#rx_udp_clock = rx_clk38 * 32 // 2 // 9 # ADC sample rate in Hertz
|
||||
sample_rate = 96000 # 96, 192, 384, 768, 1152 (for 69120/3/10)
|
||||
display_fraction = 1.00
|
||||
fft_size_multiplier = 16
|
||||
tx_ip = "192.168.1.201"
|
||||
tx_audio_port = 0xBC79
|
||||
add_imd_button = 1
|
||||
add_fdx_button = 1
|
||||
@@ -0,0 +1,498 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys, struct, socket, traceback
|
||||
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
import _quisk as QS
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
class Adf4351: # class to hold adf4351 attributes
|
||||
def __init__(self, receiver, clock, r_counter):
|
||||
self.receiver = receiver
|
||||
self.clock = clock
|
||||
self.r_counter = r_counter
|
||||
self.int_mode = 1 # integer one, fractional zero
|
||||
self.band_sel_clock_div = 40
|
||||
self.aux_rf_out = 0b000 # enable 1/0, power 00 to 11
|
||||
self.frac_value = 0
|
||||
self.modulus = 23
|
||||
self.changed = 0
|
||||
|
||||
class Preamp: # Lone Wire Bus control of preamp for 2 meters and 70 cm
|
||||
def __init__(self):
|
||||
self.IP = '192.168.1.194'
|
||||
self.PORT = 0x3A00 + 67
|
||||
# Create a socket for the Lone Wire Bus control of the preamp
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.connect((self.IP, self.PORT))
|
||||
self.socket.settimeout(0)
|
||||
self.want_preamp = self.have_preamp = '\000'
|
||||
def ChangeBand(self, band):
|
||||
if band == '2':
|
||||
self.want_preamp = '\001'
|
||||
elif band == '70cm':
|
||||
self.want_preamp = '\002'
|
||||
else:
|
||||
self.want_preamp = '\000'
|
||||
#print ("ChangeBand", band)
|
||||
def HeartBeat(self):
|
||||
try:
|
||||
data = self.socket.recv(4096)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if len(data) == 1:
|
||||
self.have_preamp = data
|
||||
#print ("Got data 0x%X" % ord(data))
|
||||
if self.want_preamp != self.have_preamp:
|
||||
self.socket.send(self.want_preamp)
|
||||
#print ("Send data 0x%X" % ord(self.want_preamp))
|
||||
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
self.vfo_frequency = 52000000
|
||||
self.vfo_sample_rate = conf.sample_rate
|
||||
self.vfo_test = 0 # JIM
|
||||
self.tx_frequency = 0
|
||||
self.CorrectTxDc = {
|
||||
'23cm':(1270.0, 0.167081, 0.150557),
|
||||
'2':(146.0, 0.018772, 0.038658),
|
||||
'33cm':(915.0, 0.140150, 0.051967),
|
||||
'6':(52.0, 0.020590, 0.024557),
|
||||
'70cm':(435.0, 0.004495, 0.096879),
|
||||
'1.25':(223.5, 0.042958, 0.055212),
|
||||
}
|
||||
self.rx_clock38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
|
||||
#rx_udp_clock = rx_clock38 * 32 // 2 // 9 # ADC sample rate in Hertz
|
||||
self.rx_udp_clock_nominal = 69120000 # rate to display
|
||||
self.tx_clock80 = 80000000 + 14
|
||||
self.firmware_version = None # firmware version is initially unknown
|
||||
self.rx_udp_socket = None
|
||||
self.tx_udp_socket = None
|
||||
self.got_rx_udp_status = ''
|
||||
self.got_tx_udp_status = ''
|
||||
self.band = ''
|
||||
self.rx_phase0 = self.rx_phase1 = 0
|
||||
self.tx_phase = 0
|
||||
self.button_PTT = 0
|
||||
self.mode_is_cw = 0
|
||||
self.scan_enable = 0
|
||||
self.scan_blocks = 0
|
||||
self.scan_samples = 1
|
||||
self.scan_phase = 0
|
||||
self.fft_scan_valid = 0.84
|
||||
self.preamp = Preamp()
|
||||
self.Rx4351 = Adf4351(True, self.rx_clock38, 8)
|
||||
self.Tx4351 = Adf4351(False, 10700000, 2)
|
||||
self.Tx4351.aux_rf_out = 0b000 # enable aux RF out 0b111 or turn off 0b000
|
||||
self.decim3 = 10
|
||||
self.SetDecim(192000)
|
||||
self.var_rates = ['31X', '19X', '9X', '5X', '3X', '2X', '1728', '1152', '768', '384', '192', '96', '48'] # supported sample rates as strings
|
||||
self.index = 0
|
||||
self.repeater_freq = None
|
||||
self.DcI, self.DcQ = (0.0, 0.0)
|
||||
self.NewAdf4351(self.Rx4351, 146E6)
|
||||
self.NewAdf4351(self.Tx4351, 146E6)
|
||||
self.NewAd9951(52e6)
|
||||
self.NewUdpStatus()
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.tx_frequency = tx_freq
|
||||
if not self.Rx4351.frequency - 3E6 < vfo_freq < self.Rx4351.frequency + 3E6:
|
||||
self.NewAdf4351(self.Rx4351, vfo_freq)
|
||||
self.vfo_frequency = -1
|
||||
self.NewAd9951(tx_freq)
|
||||
if abs(self.ad9951_freq - 10.7e6) > 15000:
|
||||
self.NewAdf4351(self.Tx4351, tx_freq)
|
||||
self.NewAd9951(tx_freq)
|
||||
self.NewAd9951(tx_freq)
|
||||
if self.vfo_frequency != vfo_freq:
|
||||
self.vfo_frequency = vfo_freq
|
||||
self.scan_deltaf = int(1152E3 * self.fft_scan_valid + 0.5)
|
||||
self.scan_phase = int(1152.E3 * self.fft_scan_valid / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
self.scan_vfo0 = vfo_freq
|
||||
rx_phase1 = int((vfo_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
if self.scan_enable:
|
||||
self.scan_vfo0 = self.scan_vfo0 - self.scan_deltaf * (self.scan_blocks - 1) // 2
|
||||
rx_phase1 = rx_phase1 - int(self.scan_phase * (self.scan_blocks - 1) / 2.0 + 0.5)
|
||||
self.rx_phase1 = rx_phase1 & 0xFFFFFFFF
|
||||
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
|
||||
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, vfo_freq)
|
||||
QS.change_scan(self.scan_blocks, 1152000, self.fft_scan_valid, self.scan_vfo0, self.scan_deltaf)
|
||||
if DEBUG:
|
||||
#print( "vfo", vfo_freq, "adf4351", self.Rx4351.frequency, "phase", rx_phase1, "rx_tune", self.Rx4351.frequency - vfo_freq, rx_tune_freq)
|
||||
#print ("VFO", self.Rx4351.frequency + rx_tune_freq)
|
||||
print ("Change to Tx %d Vfo %d; VFO %.0f = adf4351_freq %.0f + rx_tune_freq %.0f" % (tx_freq, vfo_freq,
|
||||
self.Rx4351.frequency + rx_tune_freq, self.Rx4351.frequency, rx_tune_freq))
|
||||
#print ("scan_enable %d, scan_blocks %d, scan_vfo0 %d, scan_deltaf %d" % (self.scan_enable, self.scan_blocks, self.scan_vfo0, self.scan_deltaf))
|
||||
else:
|
||||
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, self.vfo_frequency)
|
||||
rx_phase0 = int((tx_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
self.rx_phase0 = rx_phase0 & 0xFFFFFFFF
|
||||
self.NewUdpStatus()
|
||||
if self.application.bottom_widgets:
|
||||
Rx1 = self.Rx4351.frequency * 1e-6
|
||||
Rx2 = (self.ReturnVfoFloat() - self.Rx4351.frequency) * 1e-6
|
||||
t = "Rx Div %d; ADF4351 %.6f + rx_tune %.6f = %.6f Tx Adf4351 %.6f AD9951 %.6f" % (
|
||||
2**self.Rx4351.rf_divider, Rx1, Rx2, Rx1 + Rx2, self.Tx4351.frequency * 1e-6, self.ad9951_freq * 1e-6)
|
||||
self.application.bottom_widgets.UpdateText(t)
|
||||
return tx_freq, vfo_freq
|
||||
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
|
||||
if offset is None: # Return True if frequency change is complete
|
||||
self.HeartBeat()
|
||||
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
|
||||
if offset == 0: # Change back to the original frequency
|
||||
if self.repeater_freq is None: # Frequency was already reset
|
||||
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
|
||||
self.ChangeFrequency(self.repeater_freq, self.vfo_frequency)
|
||||
self.repeater_freq = None
|
||||
else: # Shift to repeater input frequency
|
||||
self.repeater_freq = self.tx_frequency
|
||||
offset = int(offset * 1000) # Convert kHz to Hz
|
||||
self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency)
|
||||
return False
|
||||
def ReturnVfoFloat(self): # Return the accurate VFO as a float
|
||||
rx_phase1 = int((self.vfo_frequency - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
|
||||
return self.Rx4351.frequency + rx_tune_freq
|
||||
def open(self):
|
||||
##self.application.config_screen.config.tx_phase.Enable(1)
|
||||
# Create the proper broadcast address for rx_udp_ip.
|
||||
nm = self.conf.rx_udp_ip_netmask.split('.')
|
||||
ip = self.conf.rx_udp_ip.split('.')
|
||||
nm = list(map(int, nm))
|
||||
ip = list(map(int, ip))
|
||||
bc = ''
|
||||
for i in range(4):
|
||||
x = (ip[i] | ~ nm[i]) & 0xFF
|
||||
bc = bc + str(x) + '.'
|
||||
self.broadcast_addr = bc[:-1]
|
||||
# This socket is used for the Simple Network Discovery Protocol by AE4JY
|
||||
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket_sndp.setblocking(0)
|
||||
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self.sndp_request = chr(56) + chr(0) + chr(0x5A) + chr(0xA5) + chr(0) * 52
|
||||
self.sndp_rx_active = True
|
||||
# conf.rx_udp_port is used for returning ADC samples
|
||||
# conf.rx_udp_port + 1 is used for control
|
||||
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.rx_udp_socket.setblocking(0)
|
||||
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
|
||||
# conf.tx_audio_port + 1 is used for control
|
||||
if self.conf.tx_ip:
|
||||
self.sndp_tx_active = True
|
||||
self.tx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.tx_udp_socket.setblocking(0)
|
||||
self.tx_udp_socket.connect((self.conf.tx_ip, self.conf.tx_audio_port + 1))
|
||||
else:
|
||||
self.sndp_tx_active = False
|
||||
QS.change_rates(96000, 0, 96000, 0)
|
||||
self.application.test1Button.Enable(0)
|
||||
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
|
||||
def close(self):
|
||||
if self.rx_udp_socket:
|
||||
self.rx_udp_socket.close()
|
||||
self.rx_udp_socket = None
|
||||
if self.tx_udp_socket:
|
||||
self.tx_udp_socket.close()
|
||||
self.tx_udp_socket = None
|
||||
def PrintStatus(self, msg, string):
|
||||
print (msg, ' ', end=' ')
|
||||
print (string[0:2], end=' ')
|
||||
for c in string[2:]:
|
||||
print ("%2X" % ord(c), end=' ')
|
||||
print ()
|
||||
def GetFirmwareVersion(self):
|
||||
return self.firmware_version
|
||||
def ChangeMode(self, mode):
|
||||
# mode is a string: "USB", "AM", etc.
|
||||
if mode in ("CWL", "CWU"):
|
||||
self.mode_is_cw = 1
|
||||
else:
|
||||
self.mode_is_cw = 0
|
||||
self.NewUdpStatus()
|
||||
def ChangeBand(self, band):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
self.band = band
|
||||
try:
|
||||
freq, DcI, DcQ = self.CorrectTxDc[band]
|
||||
except KeyError:
|
||||
DcI, DcQ = (0.0, 0.0)
|
||||
self.NewUdpCorrect(DcI, DcQ)
|
||||
self.preamp.ChangeBand(band)
|
||||
def NewUdpCorrect(self, DcI, DcQ):
|
||||
self.DcI = DcI
|
||||
self.DcQ = DcQ
|
||||
QS.set_udp_tx_correct(DcI, DcQ, 0.828)
|
||||
self.NewUdpStatus()
|
||||
def PrintUdpCorrect(self):
|
||||
for band in self.CorrectTxDc:
|
||||
freq, DcI, DcQ = self.CorrectTxDc[band]
|
||||
print ("'%s':(%.1f, %.6f, %.6f)," % (band, freq, DcI, DcQ))
|
||||
def OnButtonPTT(self, event):
|
||||
btn = event.GetEventObject()
|
||||
if btn.GetValue(): # Turn the software key bit on or off
|
||||
self.button_PTT = 1
|
||||
else:
|
||||
self.button_PTT = 0
|
||||
QS.set_key_down(self.button_PTT)
|
||||
self.NewUdpStatus()
|
||||
def OnSpot(self, level):
|
||||
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
||||
pass
|
||||
def Sndp(self): # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
|
||||
try:
|
||||
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
|
||||
except:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
return
|
||||
for i in range(5):
|
||||
try:
|
||||
data = self.socket_sndp.recv(1024)
|
||||
except:
|
||||
break
|
||||
if len(data) != 56:
|
||||
continue
|
||||
if data[5:17] == 'QuiskUHFR-v1':
|
||||
ip = self.conf.rx_udp_ip.split('.')
|
||||
ip = list(map(int, ip))
|
||||
ip = list(map(chr, ip))
|
||||
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
|
||||
self.sndp_rx_active = False
|
||||
if DEBUG: print("SNDP success for Rx")
|
||||
else:
|
||||
t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0]
|
||||
+ chr(0) * 12 + chr(self.conf.rx_udp_port & 0xFF) + chr(self.conf.rx_udp_port >> 8) + chr(0))
|
||||
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
|
||||
elif data[5:17] == 'QuiskUHFT-v1':
|
||||
if self.conf.tx_ip:
|
||||
ip = self.conf.tx_ip.split('.')
|
||||
ip = list(map(int, ip))
|
||||
ip = list(map(chr, ip))
|
||||
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
|
||||
self.sndp_tx_active = False
|
||||
if DEBUG: print("SNDP success for Tx")
|
||||
else:
|
||||
t = (data[0:4] + chr(2) + data[5:37] + ip[3] + ip[2] + ip[1] + ip[0]
|
||||
+ chr(0) * 12 + chr(self.conf.tx_audio_port & 0xFF) + chr(self.conf.tx_audio_port >> 8) + chr(0))
|
||||
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
|
||||
def HeartBeat(self):
|
||||
if self.sndp_rx_active or self.sndp_tx_active:
|
||||
self.Sndp()
|
||||
return # SNDP is required
|
||||
for i in range(10):
|
||||
try: # receive the Rx status if any
|
||||
data = self.rx_udp_socket.recv(1024)
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus(' gotRx ', data)
|
||||
except:
|
||||
break
|
||||
else:
|
||||
if data[0:2] == 'Sx':
|
||||
self.got_rx_udp_status = data
|
||||
if self.tx_udp_socket:
|
||||
for i in range(10):
|
||||
try: # receive the Tx status if any
|
||||
data = self.tx_udp_socket.recv(1024)
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus(' gotTx ', data)
|
||||
except:
|
||||
break
|
||||
else:
|
||||
if data[0:2] == 'Sx':
|
||||
self.got_tx_udp_status = data
|
||||
if self.want_rx_udp_status[16:] == self.got_rx_udp_status[16:]: # The first part returns information from the hardware
|
||||
self.firmware_version = ord(self.got_rx_udp_status[2]) # Firmware version is returned here
|
||||
self.Rx4351.changed = 0
|
||||
else:
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus('HaveRx', self.got_rx_udp_status[0:20])
|
||||
self.PrintStatus('sendRx', self.want_rx_udp_status[0:20])
|
||||
try:
|
||||
self.rx_udp_socket.send(self.want_rx_udp_status)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
if not self.tx_udp_socket:
|
||||
pass
|
||||
elif self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]: # The first part returns information from the hardware
|
||||
self.Tx4351.changed = 0
|
||||
self.Tx9951_changed = 0
|
||||
else:
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus('HaveTx', self.got_rx_udp_status[0:20])
|
||||
self.PrintStatus('sendTx', self.want_rx_udp_status[0:20])
|
||||
try:
|
||||
self.tx_udp_socket.send(self.want_rx_udp_status)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
if 0:
|
||||
self.rx_udp_socket.send('Qs')
|
||||
self.preamp.HeartBeat()
|
||||
def VarDecimGetChoices(self): # return text labels for the control
|
||||
return self.var_rates
|
||||
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 VarDecimRange(self):
|
||||
return (48000, 1152000)
|
||||
def VarDecimSet(self, index=None): # set decimation, return sample rate
|
||||
if index is None: # initial call to set decimation before the call to open()
|
||||
rate = self.application.vardecim_set # May be None or from different hardware
|
||||
try:
|
||||
rate = rate // 1000
|
||||
if rate > 1152:
|
||||
rate = 1152
|
||||
index = self.var_rates.index(str(rate))
|
||||
except:
|
||||
rate = 192
|
||||
index = self.var_rates.index(str(rate))
|
||||
self.index = index
|
||||
rate = self.var_rates[index]
|
||||
if rate[-1] == 'X':
|
||||
self.scan_enable = 1
|
||||
self.scan_blocks = int(rate[0:-1])
|
||||
self.scan_samples = self.application.fft_size
|
||||
self.decim1 = 2
|
||||
self.decim2 = 3
|
||||
rate = 1152000 * self.scan_blocks
|
||||
else:
|
||||
self.scan_enable = 0
|
||||
self.scan_blocks = 0
|
||||
rate = int(rate)
|
||||
rate = rate * 1000
|
||||
self.SetDecim(rate)
|
||||
vfo = self.vfo_frequency
|
||||
self.vfo_frequency = -1
|
||||
self.vfo_sample_rate = rate
|
||||
self.ChangeFrequency(self.tx_frequency, vfo)
|
||||
self.NewUdpStatus()
|
||||
return rate
|
||||
def SetDecim(self, rate):
|
||||
# self.decim1, decim2, decim3 are the first, second, third decimations in the hardware
|
||||
if rate >= 1152000:
|
||||
self.decim1 = 2
|
||||
elif rate >= 192000:
|
||||
self.decim1 = 3
|
||||
elif rate == 96000:
|
||||
self.decim1 = 6
|
||||
else:
|
||||
self.decim1 = 12
|
||||
self.decim2 = self.rx_udp_clock_nominal // rate // self.decim1 // self.decim3
|
||||
def NewUdpStatus(self):
|
||||
# Start of 16 bytes sent to the hardware:
|
||||
s = "Sx" # 0:2 Fixed string
|
||||
s += chr(0) # 2 Version number is returned here
|
||||
s += chr(0) # 3
|
||||
s += chr(0) * 12 # 4:16
|
||||
# Start of 80 bytes of data sent to the hardware:
|
||||
s += chr( 6 - 1) # 0 Variable decimation less one channel 0 first
|
||||
s += chr(12 - 1) # 1 Variable decimation less one channel 0 second
|
||||
s += struct.pack("<L", self.rx_phase0) # 2: 6 Channel zero Rx tune phase
|
||||
s += struct.pack("<L", self.rx_phase1) # 6:10 Channel one Rx tune phase)
|
||||
s += chr(0x3 | self.scan_enable << 2 | self.Rx4351.changed << 3 |
|
||||
self.Tx4351.changed << 4 | self.Tx9951_changed << 5 |
|
||||
self.button_PTT << 6 | self.mode_is_cw << 7) # 10 Flags
|
||||
# 0: enable samples on channel 0
|
||||
# 1: enable samples on channel 1
|
||||
# 2: enable scan on channel 1
|
||||
# 3: the receive adf4351 registers have changed
|
||||
# 4: the transmit adf4351 registers have changed
|
||||
# 5: the transmit ad9951 register changed
|
||||
# 6: the PTT button is down
|
||||
# 7: the mode is CWU or CWL
|
||||
s += chr(self.scan_blocks) # 11 For scan, the number of frequency blocks
|
||||
s += struct.pack("<H", self.scan_samples) # 12:14 For scan, the number of samples per block
|
||||
s += struct.pack("<L", self.scan_phase) # 14:18 For scan, the tuning phase increment
|
||||
s += chr(self.decim1 - 1) # 18 Variable decimation less one channel 1 first
|
||||
s += chr(self.decim2 - 1) # 19 Variable decimation less one channel 1 second
|
||||
s += self.Rx4351.regs # 20:44 Receive adf4351; six 32-bit registers, 24 bytes
|
||||
s += self.Tx4351.regs # 44:68 Transmit adf4351; six 32-bit registers, 24 bytes
|
||||
s += self.ad9951_data # 68:74 Transmit ad9951: data length, instruction, 4 bytes of data
|
||||
DcI = int(self.DcI * 32767.0)
|
||||
DcQ = int(self.DcQ * 32767.0)
|
||||
s += struct.pack("<h", DcI) # 74:76 Transmit DC correction for I channel
|
||||
s += struct.pack("<h", DcQ) # 76:78 Transmit DC correction for Q channel
|
||||
s += chr(0) * (96 - len(s)) # Fixed length message 16 + 80
|
||||
self.want_rx_udp_status = s
|
||||
def NewAd9951(self, tx_freq):
|
||||
# Fpfd = Fref / 2 / R = Fvco / N
|
||||
# Fout = Fvco / 2**D and is twice the transmit frequency
|
||||
# Fref = 2R(2**d)Fout / N
|
||||
# Fout = Fref * N / 2 / R / 2**D
|
||||
adf = self.Tx4351
|
||||
freq = 2.0 * adf.r_counter * (2 ** adf.rf_divider) / adf.int_value * (tx_freq * 2.0)
|
||||
phase = int(freq / self.tx_clock80 * 2.0**32 + 0.5)
|
||||
try:
|
||||
self.ad9951_data = chr(40) + struct.pack("<L", phase ) + chr(4)
|
||||
except struct.error:
|
||||
self.ad9951_data = chr(0) * 6
|
||||
self.ad9951_freq = float(phase) * self.tx_clock80 / 2 ** 32
|
||||
##adf.frequency = 0.5 * self.ad9951_freq * adf.int_value / 2.0 / adf.r_counter / (2 ** adf.rf_divider)
|
||||
self.Tx9951_changed = 1
|
||||
def NewAdf4351(self, adf, vfo_freq):
|
||||
# Set the adf4351 to the nearest integer-mode frequency
|
||||
Fpfd = adf.clock / 2.0 / adf.r_counter
|
||||
if adf.receiver:
|
||||
vfo_freq += Fpfd * self.vfo_test / 8 # test the VFO at an offset from the center
|
||||
vfo = vfo_freq * 2 # Local oscillator runs at 2X frequency
|
||||
for div in range(0, 7):
|
||||
Fvco = vfo * 2 ** div
|
||||
if 2200E6 <= Fvco < 4400E6:
|
||||
adf.rf_divider = div
|
||||
adf.int_value = int(vfo * 2 ** div / Fpfd + 0.5)
|
||||
break
|
||||
else:
|
||||
if vfo < 500e6:
|
||||
adf.rf_divider = 6
|
||||
adf.int_value = int(2200E6 / Fpfd)
|
||||
else:
|
||||
adf.rf_divider = 0
|
||||
adf.int_value = int(4400E6 / Fpfd)
|
||||
adf.frequency = 0.5 * Fpfd * adf.int_value / 2 ** div
|
||||
if DEBUG:
|
||||
print ("int, div, Fvco, ADF4351", adf.int_value, div, int(vfo * 2 ** div / 1e6), adf.frequency)
|
||||
print ("New adf4351_freq", adf.frequency)
|
||||
# Number of bits for each field:
|
||||
# intNmode 1
|
||||
# aux_rf_out 3
|
||||
# rf_divider 3
|
||||
# band_sel_clock_div 8
|
||||
# r_counter 10 Fpfd = Fref / 2 / r_counter
|
||||
# int_value 16 Fvco = Fpfd * (int_value + frac_value / modulus)
|
||||
# frac_value 12
|
||||
# modulus 12
|
||||
reg = 0b00000000000000000000000000000000 # Register 0
|
||||
reg = reg | adf.int_value << 15 | adf.frac_value << 3
|
||||
s = struct.pack("<L", reg)
|
||||
reg = 0b00001000000000001000000000000001 # Register 1
|
||||
reg = reg | adf.modulus << 3
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000001000000000001111001000010 # Register 2
|
||||
reg = reg | adf.r_counter << 14 | adf.int_mode << 8 | adf.int_mode << 7
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000000001000000000000000011 # Register 3
|
||||
reg = reg | adf.int_mode << 22 | adf.int_mode << 21
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000100000000000010000111100 # Register 4
|
||||
reg = reg | adf.rf_divider << 20 | adf.band_sel_clock_div << 12 | adf.aux_rf_out << 6
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000010110000000000000000101 # Register 5
|
||||
s += struct.pack("<L", reg)
|
||||
adf.regs = s
|
||||
adf.changed = 1
|
||||
def TestVfoPlus(self, event):
|
||||
self.vfo_test += 1
|
||||
self.Rx4351.frequency = 1
|
||||
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
|
||||
def TestVfoMinus(self, event):
|
||||
self.vfo_test -= 1
|
||||
self.Rx4351.frequency = 1
|
||||
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
|
||||
@@ -0,0 +1,526 @@
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import sys, struct, socket, traceback
|
||||
|
||||
from quisk_hardware_model import Hardware as BaseHardware
|
||||
import _quisk as QS
|
||||
|
||||
DEBUG = 0
|
||||
|
||||
class Adf4351: # class to hold adf4351 attributes
|
||||
def __init__(self, receiver, clock, r_counter):
|
||||
self.receiver = receiver
|
||||
self.clock = clock
|
||||
self.r_counter = r_counter
|
||||
self.int_mode = 1 # integer one, fractional zero
|
||||
self.band_sel_clock_div = 40
|
||||
self.aux_rf_out = 0b000 # enable 1/0, power 00 to 11
|
||||
self.frac_value = 0
|
||||
self.modulus = 23
|
||||
self.changed = 0
|
||||
|
||||
class Preamp: # Lone Wire Bus control of preamp for 2 meters and 70 cm
|
||||
def __init__(self):
|
||||
self.IP = '192.168.1.194'
|
||||
self.PORT = 0x3A00 + 67
|
||||
# Create a socket for the Lone Wire Bus control of the preamp
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.connect((self.IP, self.PORT))
|
||||
self.socket.settimeout(0)
|
||||
self.want_preamp = self.have_preamp = b'\000'
|
||||
def ChangeBand(self, band):
|
||||
if band == '2':
|
||||
self.want_preamp = b'\001'
|
||||
elif band == '70cm':
|
||||
self.want_preamp = b'\002'
|
||||
else:
|
||||
self.want_preamp = b'\000'
|
||||
#print ("Preamp ChangeBand", band)
|
||||
def HeartBeat(self):
|
||||
try:
|
||||
data = self.socket.recv(4096)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if len(data) == 1:
|
||||
self.have_preamp = data
|
||||
#print ("Got data 0x%X" % ord(data))
|
||||
if self.want_preamp != self.have_preamp:
|
||||
self.socket.send(self.want_preamp)
|
||||
#print ("Send data 0x%X" % ord(self.want_preamp))
|
||||
|
||||
|
||||
class Hardware(BaseHardware):
|
||||
def __init__(self, app, conf):
|
||||
BaseHardware.__init__(self, app, conf)
|
||||
self.vfo_frequency = 52000000
|
||||
self.vfo_sample_rate = conf.sample_rate
|
||||
self.vfo_test = 0 # JIM
|
||||
self.tx_frequency = 0
|
||||
self.CorrectTxDc = {
|
||||
'23cm':(1270.0, 0.167081, 0.150557),
|
||||
'2':(146.0, 0.018772, 0.038658),
|
||||
'33cm':(915.0, 0.140150, 0.051967),
|
||||
'6':(52.0, 0.020590, 0.024557),
|
||||
'70cm':(435.0, 0.004495, 0.096879),
|
||||
'1.25':(223.5, 0.042958, 0.055212),
|
||||
}
|
||||
self.rx_clock38 = 38880000 - 30 # master clock frequency, 38880 kHz nominal
|
||||
#rx_udp_clock = rx_clock38 * 32 // 2 // 9 # ADC sample rate in Hertz
|
||||
self.rx_udp_clock_nominal = 69120000 # rate to display
|
||||
self.tx_clock80 = 80000000 + 14
|
||||
self.firmware_version = None # firmware version is initially unknown
|
||||
self.rx_udp_socket = None
|
||||
self.tx_udp_socket = None
|
||||
self.got_rx_udp_status = bytearray()
|
||||
self.got_tx_udp_status = bytearray()
|
||||
self.band = ''
|
||||
self.rx_phase0 = self.rx_phase1 = 0
|
||||
self.tx_phase = 0
|
||||
self.button_PTT = 0
|
||||
self.mode_is_cw = 0
|
||||
self.scan_enable = 0
|
||||
self.scan_blocks = 0
|
||||
self.scan_samples = 1
|
||||
self.scan_phase = 0
|
||||
self.fft_scan_valid = 0.84
|
||||
self.preamp = Preamp()
|
||||
self.Rx4351 = Adf4351(True, self.rx_clock38, 8)
|
||||
self.Tx4351 = Adf4351(False, 10700000, 2)
|
||||
self.Tx4351.aux_rf_out = 0b000 # enable aux RF out 0b111 or turn off 0b000
|
||||
self.decim3 = 10
|
||||
self.SetDecim(192000)
|
||||
self.var_rates = ['31X', '19X', '9X', '5X', '3X', '2X', '1728', '1152', '768', '384', '192', '96', '48'] # supported sample rates as strings
|
||||
self.index = 0
|
||||
self.repeater_freq = None
|
||||
self.DcI, self.DcQ = (0.0, 0.0)
|
||||
self.NewAdf4351(self.Rx4351, 146E6)
|
||||
self.NewAdf4351(self.Tx4351, 146E6)
|
||||
self.NewAd9951(52e6)
|
||||
self.NewUdpStatus()
|
||||
def ChangeFrequency(self, tx_freq, vfo_freq, source='', band='', event=None):
|
||||
self.tx_frequency = tx_freq
|
||||
if not self.Rx4351.frequency - 3E6 < vfo_freq < self.Rx4351.frequency + 3E6:
|
||||
self.NewAdf4351(self.Rx4351, vfo_freq)
|
||||
self.vfo_frequency = -1
|
||||
self.NewAd9951(tx_freq)
|
||||
if abs(self.ad9951_freq - 10.7e6) > 15000:
|
||||
self.NewAdf4351(self.Tx4351, tx_freq)
|
||||
self.NewAd9951(tx_freq)
|
||||
self.NewAd9951(tx_freq)
|
||||
if self.vfo_frequency != vfo_freq:
|
||||
self.vfo_frequency = vfo_freq
|
||||
self.scan_deltaf = int(1152E3 * self.fft_scan_valid + 0.5)
|
||||
self.scan_phase = int(1152.E3 * self.fft_scan_valid / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
self.scan_vfo0 = vfo_freq
|
||||
rx_phase1 = int((vfo_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
if self.scan_enable:
|
||||
self.scan_vfo0 = self.scan_vfo0 - self.scan_deltaf * (self.scan_blocks - 1) // 2
|
||||
rx_phase1 = rx_phase1 - int(self.scan_phase * (self.scan_blocks - 1) / 2.0 + 0.5)
|
||||
self.rx_phase1 = rx_phase1 & 0xFFFFFFFF
|
||||
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
|
||||
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, vfo_freq)
|
||||
QS.change_scan(self.scan_blocks, 1152000, self.fft_scan_valid, self.scan_vfo0, self.scan_deltaf)
|
||||
if DEBUG:
|
||||
#print( "vfo", vfo_freq, "adf4351", self.Rx4351.frequency, "phase", rx_phase1, "rx_tune", self.Rx4351.frequency - vfo_freq, rx_tune_freq)
|
||||
#print ("VFO", self.Rx4351.frequency + rx_tune_freq)
|
||||
print ("Change to Tx %d Vfo %d; VFO %.0f = adf4351_freq %.0f + rx_tune_freq %.0f" % (tx_freq, vfo_freq,
|
||||
self.Rx4351.frequency + rx_tune_freq, self.Rx4351.frequency, rx_tune_freq))
|
||||
#print ("scan_enable %d, scan_blocks %d, scan_vfo0 %d, scan_deltaf %d" % (self.scan_enable, self.scan_blocks, self.scan_vfo0, self.scan_deltaf))
|
||||
else:
|
||||
QS.change_rates(96000, tx_freq, self.vfo_sample_rate, self.vfo_frequency)
|
||||
rx_phase0 = int((tx_freq - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
self.rx_phase0 = rx_phase0 & 0xFFFFFFFF
|
||||
self.NewUdpStatus()
|
||||
if self.application.bottom_widgets:
|
||||
Rx1 = self.Rx4351.frequency * 1e-6
|
||||
Rx2 = (self.ReturnVfoFloat() - self.Rx4351.frequency) * 1e-6
|
||||
t = "Rx Div %d; ADF4351 %.6f + rx_tune %.6f = %.6f Tx Adf4351 %.6f AD9951 %.6f" % (
|
||||
2**self.Rx4351.rf_divider, Rx1, Rx2, Rx1 + Rx2, self.Tx4351.frequency * 1e-6, self.ad9951_freq * 1e-6)
|
||||
self.application.bottom_widgets.UpdateText(t)
|
||||
return tx_freq, vfo_freq
|
||||
def RepeaterOffset(self, offset=None): # Change frequency for repeater offset during Tx
|
||||
if offset is None: # Return True if frequency change is complete
|
||||
self.HeartBeat()
|
||||
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
|
||||
if offset == 0: # Change back to the original frequency
|
||||
if self.repeater_freq is None: # Frequency was already reset
|
||||
return self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]
|
||||
self.ChangeFrequency(self.repeater_freq, self.vfo_frequency)
|
||||
self.repeater_freq = None
|
||||
else: # Shift to repeater input frequency
|
||||
self.repeater_freq = self.tx_frequency
|
||||
offset = int(offset * 1000) # Convert kHz to Hz
|
||||
self.ChangeFrequency(self.tx_frequency + offset, self.vfo_frequency)
|
||||
return False
|
||||
def ReturnVfoFloat(self): # Return the accurate VFO as a float
|
||||
rx_phase1 = int((self.vfo_frequency - self.Rx4351.frequency) / self.conf.rx_udp_clock * 2.0**32 + 0.5)
|
||||
rx_tune_freq = float(rx_phase1) * self.conf.rx_udp_clock / 2.0**32
|
||||
return self.Rx4351.frequency + rx_tune_freq
|
||||
def open(self):
|
||||
##self.application.config_screen.config.tx_phase.Enable(1)
|
||||
# Create the proper broadcast address for rx_udp_ip.
|
||||
nm = self.conf.rx_udp_ip_netmask.split('.')
|
||||
ip = self.conf.rx_udp_ip.split('.')
|
||||
nm = list(map(int, nm))
|
||||
ip = list(map(int, ip))
|
||||
bc = ''
|
||||
for i in range(4):
|
||||
x = (ip[i] | ~ nm[i]) & 0xFF
|
||||
bc = bc + str(x) + '.'
|
||||
self.broadcast_addr = bc[:-1]
|
||||
# This socket is used for the Simple Network Discovery Protocol by AE4JY
|
||||
self.socket_sndp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket_sndp.setblocking(0)
|
||||
self.socket_sndp.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self.sndp_request = bytearray()
|
||||
self.sndp_request.append(56)
|
||||
self.sndp_request.append(0)
|
||||
self.sndp_request.append(0x5A)
|
||||
self.sndp_request.append(0xA5)
|
||||
self.sndp_request += bytearray(52)
|
||||
self.sndp_rx_active = True
|
||||
# conf.rx_udp_port is used for returning ADC samples
|
||||
# conf.rx_udp_port + 1 is used for control
|
||||
self.rx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.rx_udp_socket.setblocking(0)
|
||||
self.rx_udp_socket.connect((self.conf.rx_udp_ip, self.conf.rx_udp_port + 1))
|
||||
# conf.tx_audio_port + 1 is used for control
|
||||
if self.conf.tx_ip:
|
||||
self.sndp_tx_active = True
|
||||
self.tx_udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.tx_udp_socket.setblocking(0)
|
||||
self.tx_udp_socket.connect((self.conf.tx_ip, self.conf.tx_audio_port + 1))
|
||||
else:
|
||||
self.sndp_tx_active = False
|
||||
QS.change_rates(96000, 0, 96000, 0)
|
||||
self.application.test1Button.Enable(0)
|
||||
return QS.open_rx_udp(self.conf.rx_udp_ip, self.conf.rx_udp_port)
|
||||
def close(self):
|
||||
if self.rx_udp_socket:
|
||||
self.rx_udp_socket.close()
|
||||
self.rx_udp_socket = None
|
||||
if self.tx_udp_socket:
|
||||
self.tx_udp_socket.close()
|
||||
self.tx_udp_socket = None
|
||||
def PrintStatus(self, msg, string):
|
||||
print (msg, ' ', end=' ')
|
||||
print (string[0:2], end=' ')
|
||||
for c in string[2:]:
|
||||
print ("%2X" % ord(c), end=' ')
|
||||
print ()
|
||||
def GetFirmwareVersion(self):
|
||||
return self.firmware_version
|
||||
def ChangeMode(self, mode):
|
||||
# mode is a string: "USB", "AM", etc.
|
||||
if mode in ("CWL", "CWU"):
|
||||
self.mode_is_cw = 1
|
||||
else:
|
||||
self.mode_is_cw = 0
|
||||
self.NewUdpStatus()
|
||||
def ChangeBand(self, band):
|
||||
# band is a string: "60", "40", "WWV", etc.
|
||||
self.band = band
|
||||
try:
|
||||
freq, DcI, DcQ = self.CorrectTxDc[band]
|
||||
except KeyError:
|
||||
DcI, DcQ = (0.0, 0.0)
|
||||
self.NewUdpCorrect(DcI, DcQ)
|
||||
self.preamp.ChangeBand(band)
|
||||
def NewUdpCorrect(self, DcI, DcQ):
|
||||
self.DcI = DcI
|
||||
self.DcQ = DcQ
|
||||
QS.set_udp_tx_correct(DcI, DcQ, 0.828)
|
||||
self.NewUdpStatus()
|
||||
def PrintUdpCorrect(self):
|
||||
for band in self.CorrectTxDc:
|
||||
freq, DcI, DcQ = self.CorrectTxDc[band]
|
||||
print ("'%s':(%.1f, %.6f, %.6f)," % (band, freq, DcI, DcQ))
|
||||
def OnButtonPTT(self, event):
|
||||
btn = event.GetEventObject()
|
||||
if btn.GetValue(): # Turn the software key bit on or off
|
||||
self.button_PTT = 1
|
||||
else:
|
||||
self.button_PTT = 0
|
||||
QS.set_key_down(self.button_PTT)
|
||||
self.NewUdpStatus()
|
||||
def OnSpot(self, level):
|
||||
# level is -1 for Spot button Off; else the Spot level 0 to 1000.
|
||||
pass
|
||||
def Sndp(self): # AE4JY Simple Network Discovery Protocol - attempt to set the FPGA IP address
|
||||
try:
|
||||
self.socket_sndp.sendto(self.sndp_request, (self.broadcast_addr, 48321))
|
||||
except:
|
||||
if DEBUG:
|
||||
traceback.print_exc()
|
||||
return
|
||||
for i in range(5):
|
||||
try:
|
||||
data = self.socket_sndp.recv(1024)
|
||||
except:
|
||||
break
|
||||
if len(data) != 56:
|
||||
continue
|
||||
if data[5:17] == b'QuiskUHFR-v1':
|
||||
ip = self.conf.rx_udp_ip.split('.')
|
||||
ip = list(map(int, ip))
|
||||
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
|
||||
self.sndp_rx_active = False
|
||||
if DEBUG: print("SNDP success for Rx")
|
||||
else:
|
||||
t = bytearray()
|
||||
t += data[0:4]
|
||||
t.append(2)
|
||||
t += data[5:37]
|
||||
t.append(ip[3])
|
||||
t.append(ip[2])
|
||||
t.append(ip[1])
|
||||
t.append(ip[0])
|
||||
t += bytearray(12)
|
||||
t.append(self.conf.rx_udp_port & 0xFF)
|
||||
t.append(self.conf.rx_udp_port >> 8)
|
||||
t.append(0)
|
||||
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
|
||||
elif data[5:17] == b'QuiskUHFT-v1':
|
||||
if self.conf.tx_ip:
|
||||
ip = self.conf.tx_ip.split('.')
|
||||
ip = list(map(int, ip))
|
||||
if data[37] == ip[3] and data[38] == ip[2] and data[39] == ip[1] and data[40] == ip[0]:
|
||||
self.sndp_tx_active = False
|
||||
if DEBUG: print("SNDP success for Tx")
|
||||
else:
|
||||
t = bytearray()
|
||||
t += data[0:4]
|
||||
t.append(2)
|
||||
t += data[5:37]
|
||||
t.append(ip[3])
|
||||
t.append(ip[2])
|
||||
t.append(ip[1])
|
||||
t.append(ip[0])
|
||||
t += bytearray(12)
|
||||
t.append(self.conf.tx_audio_port & 0xFF)
|
||||
t.append(self.conf.tx_audio_port >> 8)
|
||||
t.append(0)
|
||||
self.socket_sndp.sendto(t, (self.broadcast_addr, 48321))
|
||||
def HeartBeat(self):
|
||||
if self.sndp_rx_active or self.sndp_tx_active:
|
||||
if DEBUG: print ("Try SNDP", self.sndp_rx_active, self.sndp_tx_active)
|
||||
self.Sndp()
|
||||
return # SNDP is required
|
||||
for i in range(10):
|
||||
try: # receive the Rx status if any
|
||||
data = self.rx_udp_socket.recv(1024)
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus(' gotRx ', data)
|
||||
except:
|
||||
break
|
||||
else:
|
||||
if data[0:2] == b'Sx':
|
||||
self.got_rx_udp_status = data
|
||||
if self.tx_udp_socket:
|
||||
for i in range(10):
|
||||
try: # receive the Tx status if any
|
||||
data = self.tx_udp_socket.recv(1024)
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus(' gotTx ', data)
|
||||
except:
|
||||
break
|
||||
else:
|
||||
if data[0:2] == b'Sx':
|
||||
self.got_tx_udp_status = data
|
||||
if self.want_rx_udp_status[16:] == self.got_rx_udp_status[16:]: # The first part returns information from the hardware
|
||||
self.firmware_version = self.got_rx_udp_status[2] # Firmware version is returned here
|
||||
self.Rx4351.changed = 0
|
||||
else:
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus('HaveRx', self.got_rx_udp_status[0:20])
|
||||
self.PrintStatus('sendRx', self.want_rx_udp_status[0:20])
|
||||
try:
|
||||
self.rx_udp_socket.send(self.want_rx_udp_status)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
if not self.tx_udp_socket:
|
||||
pass
|
||||
elif self.want_rx_udp_status[16:] == self.got_tx_udp_status[16:]: # The first part returns information from the hardware
|
||||
self.Tx4351.changed = 0
|
||||
self.Tx9951_changed = 0
|
||||
else:
|
||||
if DEBUG > 1:
|
||||
self.PrintStatus('HaveTx', self.got_rx_udp_status[0:20])
|
||||
self.PrintStatus('sendTx', self.want_rx_udp_status[0:20])
|
||||
try:
|
||||
self.tx_udp_socket.send(self.want_rx_udp_status)
|
||||
except:
|
||||
#traceback.print_exc()
|
||||
pass
|
||||
if 0:
|
||||
self.rx_udp_socket.send(b'Qs')
|
||||
self.preamp.HeartBeat()
|
||||
def VarDecimGetChoices(self): # return text labels for the control
|
||||
return self.var_rates
|
||||
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 VarDecimRange(self):
|
||||
return (48000, 1152000)
|
||||
def VarDecimSet(self, index=None): # set decimation, return sample rate
|
||||
if index is None: # initial call to set decimation before the call to open()
|
||||
rate = self.application.vardecim_set # May be None or from different hardware
|
||||
try:
|
||||
rate = rate // 1000
|
||||
if rate > 1152:
|
||||
rate = 1152
|
||||
index = self.var_rates.index(str(rate))
|
||||
except:
|
||||
rate = 192
|
||||
index = self.var_rates.index(str(rate))
|
||||
self.index = index
|
||||
rate = self.var_rates[index]
|
||||
if rate[-1] == 'X':
|
||||
self.scan_enable = 1
|
||||
self.scan_blocks = int(rate[0:-1])
|
||||
self.scan_samples = self.application.fft_size
|
||||
self.decim1 = 2
|
||||
self.decim2 = 3
|
||||
rate = 1152000 * self.scan_blocks
|
||||
else:
|
||||
self.scan_enable = 0
|
||||
self.scan_blocks = 0
|
||||
rate = int(rate)
|
||||
rate = rate * 1000
|
||||
self.SetDecim(rate)
|
||||
vfo = self.vfo_frequency
|
||||
self.vfo_frequency = -1
|
||||
self.vfo_sample_rate = rate
|
||||
self.ChangeFrequency(self.tx_frequency, vfo)
|
||||
self.NewUdpStatus()
|
||||
return rate
|
||||
def SetDecim(self, rate):
|
||||
# self.decim1, decim2, decim3 are the first, second, third decimations in the hardware
|
||||
if rate >= 1152000:
|
||||
self.decim1 = 2
|
||||
elif rate >= 192000:
|
||||
self.decim1 = 3
|
||||
elif rate == 96000:
|
||||
self.decim1 = 6
|
||||
else:
|
||||
self.decim1 = 12
|
||||
self.decim2 = self.rx_udp_clock_nominal // rate // self.decim1 // self.decim3
|
||||
def NewUdpStatus(self):
|
||||
# Start of 16 bytes sent to the hardware:
|
||||
s = bytearray()
|
||||
s += b"Sx" # 0:2 Fixed string
|
||||
s.append(0) # 2 Version number is returned here
|
||||
s.append(0) # 3
|
||||
s += bytes(12) # 4:16
|
||||
# Start of 80 bytes of data sent to the hardware:
|
||||
s.append( 6 - 1) # 0 Variable decimation less one channel 0 first
|
||||
s.append(12 - 1) # 1 Variable decimation less one channel 0 second
|
||||
s += struct.pack("<L", self.rx_phase0) # 2: 6 Channel zero Rx tune phase
|
||||
s += struct.pack("<L", self.rx_phase1) # 6:10 Channel one Rx tune phase)
|
||||
s.append(0x3 | self.scan_enable << 2 | self.Rx4351.changed << 3 |
|
||||
self.Tx4351.changed << 4 | self.Tx9951_changed << 5 |
|
||||
self.button_PTT << 6 | self.mode_is_cw << 7) # 10 Flags
|
||||
# 0: enable samples on channel 0
|
||||
# 1: enable samples on channel 1
|
||||
# 2: enable scan on channel 1
|
||||
# 3: the receive adf4351 registers have changed
|
||||
# 4: the transmit adf4351 registers have changed
|
||||
# 5: the transmit ad9951 register changed
|
||||
# 6: the PTT button is down
|
||||
# 7: the mode is CWU or CWL
|
||||
s.append(self.scan_blocks) # 11 For scan, the number of frequency blocks
|
||||
s += struct.pack("<H", self.scan_samples) # 12:14 For scan, the number of samples per block
|
||||
s += struct.pack("<L", self.scan_phase) # 14:18 For scan, the tuning phase increment
|
||||
s.append(self.decim1 - 1) # 18 Variable decimation less one channel 1 first
|
||||
s.append(self.decim2 - 1) # 19 Variable decimation less one channel 1 second
|
||||
s += self.Rx4351.regs # 20:44 Receive adf4351; six 32-bit registers, 24 bytes
|
||||
s += self.Tx4351.regs # 44:68 Transmit adf4351; six 32-bit registers, 24 bytes
|
||||
s += self.ad9951_data # 68:74 Transmit ad9951: data length, instruction, 4 bytes of data
|
||||
DcI = int(self.DcI * 32767.0)
|
||||
DcQ = int(self.DcQ * 32767.0)
|
||||
s += struct.pack("<h", DcI) # 74:76 Transmit DC correction for I channel
|
||||
s += struct.pack("<h", DcQ) # 76:78 Transmit DC correction for Q channel
|
||||
s += bytearray(96 - len(s)) # Fixed length message 16 + 80
|
||||
self.want_rx_udp_status = s
|
||||
def NewAd9951(self, tx_freq):
|
||||
# Fpfd = Fref / 2 / R = Fvco / N
|
||||
# Fout = Fvco / 2**D and is twice the transmit frequency
|
||||
# Fref = 2R(2**d)Fout / N
|
||||
# Fout = Fref * N / 2 / R / 2**D
|
||||
adf = self.Tx4351
|
||||
freq = 2.0 * adf.r_counter * (2 ** adf.rf_divider) / adf.int_value * (tx_freq * 2.0)
|
||||
phase = int(freq / self.tx_clock80 * 2.0**32 + 0.5)
|
||||
try:
|
||||
self.ad9951_data = bytearray()
|
||||
self.ad9951_data.append(40)
|
||||
self.ad9951_data += struct.pack("<L", phase )
|
||||
self.ad9951_data.append(4)
|
||||
except struct.error:
|
||||
self.ad9951_data = bytearray(6)
|
||||
self.ad9951_freq = float(phase) * self.tx_clock80 / 2 ** 32
|
||||
##adf.frequency = 0.5 * self.ad9951_freq * adf.int_value / 2.0 / adf.r_counter / (2 ** adf.rf_divider)
|
||||
self.Tx9951_changed = 1
|
||||
def NewAdf4351(self, adf, vfo_freq):
|
||||
# Set the adf4351 to the nearest integer-mode frequency
|
||||
Fpfd = adf.clock / 2.0 / adf.r_counter
|
||||
if adf.receiver:
|
||||
vfo_freq += Fpfd * self.vfo_test / 8 # test the VFO at an offset from the center
|
||||
vfo = vfo_freq * 2 # Local oscillator runs at 2X frequency
|
||||
for div in range(0, 7):
|
||||
Fvco = vfo * 2 ** div
|
||||
if 2200E6 <= Fvco < 4400E6:
|
||||
adf.rf_divider = div
|
||||
adf.int_value = int(vfo * 2 ** div / Fpfd + 0.5)
|
||||
break
|
||||
else:
|
||||
if vfo < 500e6:
|
||||
adf.rf_divider = 6
|
||||
adf.int_value = int(2200E6 / Fpfd)
|
||||
else:
|
||||
adf.rf_divider = 0
|
||||
adf.int_value = int(4400E6 / Fpfd)
|
||||
adf.frequency = 0.5 * Fpfd * adf.int_value / 2 ** div
|
||||
if DEBUG:
|
||||
print ("int, div, Fvco, ADF4351", adf.int_value, div, int(vfo * 2 ** div / 1e6), adf.frequency)
|
||||
print ("New adf4351_freq", adf.frequency)
|
||||
# Number of bits for each field:
|
||||
# intNmode 1
|
||||
# aux_rf_out 3
|
||||
# rf_divider 3
|
||||
# band_sel_clock_div 8
|
||||
# r_counter 10 Fpfd = Fref / 2 / r_counter
|
||||
# int_value 16 Fvco = Fpfd * (int_value + frac_value / modulus)
|
||||
# frac_value 12
|
||||
# modulus 12
|
||||
reg = 0b00000000000000000000000000000000 # Register 0
|
||||
reg = reg | adf.int_value << 15 | adf.frac_value << 3
|
||||
s = struct.pack("<L", reg)
|
||||
reg = 0b00001000000000001000000000000001 # Register 1
|
||||
reg = reg | adf.modulus << 3
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000001000000000001111001000010 # Register 2
|
||||
reg = reg | adf.r_counter << 14 | adf.int_mode << 8 | adf.int_mode << 7
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000000001000000000000000011 # Register 3
|
||||
reg = reg | adf.int_mode << 22 | adf.int_mode << 21
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000100000000000010000111100 # Register 4
|
||||
reg = reg | adf.rf_divider << 20 | adf.band_sel_clock_div << 12 | adf.aux_rf_out << 6
|
||||
s += struct.pack("<L", reg)
|
||||
reg = 0b00000000010110000000000000000101 # Register 5
|
||||
s += struct.pack("<L", reg)
|
||||
adf.regs = s
|
||||
adf.changed = 1
|
||||
def TestVfoPlus(self, event):
|
||||
self.vfo_test += 1
|
||||
self.Rx4351.frequency = 1
|
||||
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
|
||||
def TestVfoMinus(self, event):
|
||||
self.vfo_test -= 1
|
||||
self.Rx4351.frequency = 1
|
||||
self.ChangeFrequency(self.tx_frequency, self.vfo_frequency)
|
||||
@@ -0,0 +1,145 @@
|
||||
# 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 wx, time
|
||||
import _quisk as QS
|
||||
|
||||
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.correct_screen = None
|
||||
self.num_rows_added = 1
|
||||
start_row = app.widget_row # The first available row
|
||||
start_col = app.button_start_col # The start of the button columns
|
||||
b = app.QuiskPushbutton(frame, hardware.TestVfoPlus, 'Adf+')
|
||||
bw, bh = b.GetMinSize()
|
||||
gbs.Add(b, (start_row, start_col), flag=wx.EXPAND)
|
||||
b = app.QuiskPushbutton(frame, hardware.TestVfoMinus, 'Adf-')
|
||||
gbs.Add(b, (start_row, start_col + 1), flag=wx.EXPAND)
|
||||
b = app.QuiskPushbutton(frame, self.OnBtnCorrect, 'Corr')
|
||||
gbs.Add(b, (start_row, start_col + 2), flag=wx.EXPAND)
|
||||
self.status_label = app.QuiskText(frame, 'Ready', bh)
|
||||
gbs.Add(self.status_label, (start_row, start_col + 3), (1, 24), flag=wx.EXPAND)
|
||||
def UpdateText(self, text):
|
||||
self.status_label.SetLabel(text)
|
||||
def OnBtnCorrect(self, event):
|
||||
if self.correct_screen:
|
||||
self.correct_screen.Raise()
|
||||
else:
|
||||
self.correct_screen = QCorrect(self, self.application.width)
|
||||
|
||||
class QCorrect(wx.Frame):
|
||||
"""Create a window with DC adjustment controls"""
|
||||
f_DcI = "DC adjustment for In-Phase %.6f"
|
||||
f_DcQ = "DC adjustment for Quadrature %.6f"
|
||||
def __init__(self, parent, width):
|
||||
self.parent = parent
|
||||
self.application = parent.application
|
||||
self.config = parent.config
|
||||
self.hardware = parent.hardware
|
||||
t = "DC Null Adjustment"
|
||||
wx.Frame.__init__(self, self.application.main_frame, -1, t, pos=(50, 100), style=wx.CAPTION)
|
||||
panel = wx.Panel(self)
|
||||
self.MakeControls(panel, width)
|
||||
self.Show()
|
||||
def MakeControls(self, panel, width): # Make controls for DC adjustment
|
||||
self.old_DcI = self.DcI = self.hardware.DcI
|
||||
self.old_DcQ = self.DcQ = self.hardware.DcQ
|
||||
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
|
||||
sl_max = width * 4 // 10 # maximum +/- value for slider
|
||||
self.dc_scale = float(0.3) / sl_max
|
||||
font = wx.Font(self.config.default_font_size, wx.FONTFAMILY_SWISS, wx.NORMAL,
|
||||
wx.FONTWEIGHT_NORMAL, False, self.config.quisk_typeface)
|
||||
chary = self.GetCharHeight()
|
||||
y = chary * 3 // 10
|
||||
self.t_DcI = wx.StaticText(panel, -1, self.f_DcI % self.old_DcI, pos=(0, y))
|
||||
self.t_DcI.SetFont(font)
|
||||
y += self.t_DcI.GetSize().GetHeight()
|
||||
self.DcI1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
|
||||
pos=(0, y), size=(width, -1))
|
||||
y += self.DcI1.GetSize().GetHeight()
|
||||
self.DcI2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
|
||||
pos=(0, y), size=(width, -1))
|
||||
y += self.DcI2.GetSize().GetHeight()
|
||||
self.PositionDcI(self.old_DcI)
|
||||
self.t_DcQ = wx.StaticText(panel, -1, self.f_DcQ % self.old_DcQ, pos=(0, y))
|
||||
self.t_DcQ.SetFont(font)
|
||||
y += self.t_DcQ.GetSize().GetHeight()
|
||||
self.DcQ1 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
|
||||
pos=(0, y), size=(width, -1))
|
||||
y += self.DcQ1.GetSize().GetHeight()
|
||||
self.DcQ2 = wx.Slider(panel, -1, 0, -sl_max, sl_max,
|
||||
pos=(0, y), size=(width, -1))
|
||||
y += self.DcQ2.GetSize().GetHeight()
|
||||
sv = self.application.QuiskPushbutton(panel, self.OnBtnSave, 'Save')
|
||||
cn = self.application.QuiskPushbutton(panel, self.OnBtnCancel, 'Cancel')
|
||||
w, h = cn.GetSize().Get()
|
||||
sv.SetSize((w, h))
|
||||
y += h // 4
|
||||
x = (width - w * 3) // 4
|
||||
sv.SetPosition((x, y))
|
||||
cn.SetPosition((x*3 + w*2, y))
|
||||
sv.SetBackgroundColour('light blue')
|
||||
cn.SetBackgroundColour('light blue')
|
||||
y += h
|
||||
y += h // 4
|
||||
self.DcI1.SetBackgroundColour('aquamarine')
|
||||
self.DcI2.SetBackgroundColour('orange')
|
||||
self.DcQ1.SetBackgroundColour('aquamarine')
|
||||
self.DcQ2.SetBackgroundColour('orange')
|
||||
self.PositionDcQ(self.old_DcQ)
|
||||
self.SetClientSize(wx.Size(width, y))
|
||||
self.DcI1.Bind(wx.EVT_SCROLL, self.OnChange)
|
||||
self.DcI2.Bind(wx.EVT_SCROLL, self.OnFineDcI)
|
||||
self.DcQ1.Bind(wx.EVT_SCROLL, self.OnChange)
|
||||
self.DcQ2.Bind(wx.EVT_SCROLL, self.OnFineDcQ)
|
||||
def PositionDcI(self, dc): # set pos1, pos2 for I
|
||||
pos2 = round(dc / self.dc_scale)
|
||||
remain = dc - pos2 * self.dc_scale
|
||||
pos1 = round(remain / self.dc_scale * 50.0)
|
||||
self.DcI1.SetValue(pos1)
|
||||
self.DcI2.SetValue(pos2)
|
||||
def PositionDcQ(self, dc): # set pos1, pos2 for Q
|
||||
pos2 = round(dc / self.dc_scale)
|
||||
remain = dc - pos2 * self.dc_scale
|
||||
pos1 = round(remain / self.dc_scale * 50.0)
|
||||
self.DcQ1.SetValue(pos1)
|
||||
self.DcQ2.SetValue(pos2)
|
||||
def OnChange(self, event):
|
||||
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
|
||||
if abs(dc) < self.dc_scale * 3.0 / 50.0:
|
||||
dc = 0.0
|
||||
self.t_DcI.SetLabel(self.f_DcI % dc)
|
||||
self.DcI = dc
|
||||
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
|
||||
if abs(dc) < self.dc_scale * 3.0 / 50.0:
|
||||
dc = 0.0
|
||||
self.t_DcQ.SetLabel(self.f_DcQ % dc)
|
||||
self.DcQ = dc
|
||||
self.hardware.NewUdpCorrect(self.DcI, self.DcQ)
|
||||
def OnFineDcI(self, event): # re-center the fine slider when the coarse slider is adjusted
|
||||
dc = self.dc_scale * self.DcI1.GetValue() / 50.0 + self.dc_scale * self.DcI2.GetValue()
|
||||
self.PositionDcI(dc)
|
||||
self.OnChange(event)
|
||||
def OnFineDcQ(self, event): # re-center the fine slider when the coarse slider is adjusted
|
||||
dc = self.dc_scale * self.DcQ1.GetValue() / 50.0 + self.dc_scale * self.DcQ2.GetValue()
|
||||
self.PositionDcQ(dc)
|
||||
self.OnChange(event)
|
||||
def OnBtnSave(self, event):
|
||||
self.hardware.CorrectTxDc[self.hardware.band] = (self.hardware.tx_frequency * 1E-6, self.DcI, self.DcQ)
|
||||
self.hardware.PrintUdpCorrect()
|
||||
self.parent.correct_screen = None
|
||||
self.Destroy()
|
||||
def OnBtnCancel(self, event=None):
|
||||
self.hardware.NewUdpCorrect(self.old_DcI, self.old_DcQ)
|
||||
self.parent.correct_screen = None
|
||||
self.Destroy()
|
||||
|
||||
Reference in New Issue
Block a user