quisk-kc4upr/quisk_vna.py

1417 lines
53 KiB
Python
Executable File

#! /usr/bin/python
# All QUISK software is Copyright (C) 2006-2018 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!!
"""The main program for Quisk VNA, a vector network analyzer.
Usage: python quisk_vns.py [-c | --config config_file_path]
This can also be installed as a package and run as quisk_vna.main().
"""
from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
import sys, os
os.chdir(os.path.normpath(os.path.dirname(__file__))) # change directory to the location of this script
if sys.path[0] != '': # Make sure the current working directory is on path
sys.path.insert(0, '')
import wx, wx.html, wx.lib.stattext, wx.lib.colourdb
import math, cmath, time, traceback, string, pickle
import threading, webbrowser
import _quisk as QS
from quisk_widgets import *
import configure
DEBUG = 0
# Command line parsing: be able to specify the config file.
from optparse import OptionParser
parser = OptionParser()
parser.add_option('-c', '--config', dest='config_file_path',
help='Specify the configuration file path')
parser.add_option('', '--config2', dest='config_file_path2', default='',
help='Specify a second configuration file to read after the first')
parser.add_option('-a', '--ask', action="store_true", dest='AskMe', default=False,
help='Ask which radio to use when starting')
argv_options = parser.parse_args()[0]
ConfigPath = argv_options.config_file_path # Get config file path
ConfigPath2 = argv_options.config_file_path2
if sys.platform == 'win32':
path = os.getenv('HOMEDRIVE', '') + os.getenv('HOMEPATH', '')
for dir in ("Documents", "My Documents", "Eigene Dateien", "Documenti", "Mine Dokumenter"):
config_dir = os.path.join(path, dir)
if os.path.isdir(config_dir):
break
else:
config_dir = os.path.join(path, "My Documents")
try:
try:
import winreg as Qwinreg
except ImportError:
import _winreg as Qwinreg
key = Qwinreg.OpenKey(Qwinreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders")
val = Qwinreg.QueryValueEx(key, "Personal")
val = Qwinreg.ExpandEnvironmentStrings(val[0])
Qwinreg.CloseKey(key)
if os.path.isdir(val):
DefaultConfigDir = val
else:
DefaultConfigDir = config_dir
except:
traceback.print_exc()
DefaultConfigDir = config_dir
if not ConfigPath:
ConfigPath = os.path.join(DefaultConfigDir, "quisk_conf.py")
if not os.path.isfile(ConfigPath):
path = os.path.join(config_dir, "quisk_conf.py")
if os.path.isfile(path):
ConfigPath = path
del config_dir
else:
DefaultConfigDir = os.path.expanduser('~')
if not ConfigPath:
ConfigPath = os.path.join(DefaultConfigDir, ".quisk_conf.py")
if not ConfigPath: # Use default path
if sys.platform == 'win32':
path = os.getenv('HOMEDRIVE', '') + os.getenv('HOMEPATH', '')
for dir in ("Documents", "My Documents", "Eigene Dateien", "Documenti"):
ConfigPath = os.path.join(path, dir)
if os.path.isdir(ConfigPath):
break
else:
ConfigPath = os.path.join(path, "My Documents")
ConfigPath = os.path.join(ConfigPath, "quisk_conf.py")
if not os.path.isfile(ConfigPath): # See if the user has a config file
try:
import shutil # Try to create an initial default config file
shutil.copyfile('quisk_conf_win.py', ConfigPath)
except:
pass
else:
ConfigPath = os.path.expanduser('~/.quisk_conf.py')
class SoundThread(threading.Thread):
"""Create a second (non-GUI) thread to read samples."""
def __init__(self):
self.do_init = 1
threading.Thread.__init__(self)
self.doQuit = threading.Event()
self.doQuit.clear()
def run(self):
"""Read, process, play sound; then notify the GUI thread to check for FFT data."""
if self.do_init: # Open sound using this thread
self.do_init = 0
QS.start_sound()
wx.CallAfter(application.PostStartup)
while not self.doQuit.isSet():
QS.read_sound()
wx.CallAfter(application.OnReadSound)
QS.close_sound()
def stop(self):
"""Set a flag to indicate that the sound thread should end."""
self.doQuit.set()
class GraphDisplay(wx.Window):
"""Display the graph within the graph screen."""
def __init__(self, parent, x, y, graph_width, height, chary):
wx.Window.__init__(self, parent,
pos = (x, y),
size = (graph_width, height),
style = wx.NO_BORDER)
self.parent = parent
self.chary = chary
self.graph_width = graph_width
self.line_mag = []
self.line_phase = []
self.line_swr = []
self.display_text = ""
self.SetBackgroundColour(conf.color_graph)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, parent.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, parent.OnLeftUp)
self.Bind(wx.EVT_MOTION, parent.OnMotion)
self.Bind(wx.EVT_MOUSEWHEEL, parent.OnWheel)
self.tune_tx = graph_width // 2 # Current X position of the Tx tuning line
self.height = 10
self.y_min = 1000
self.y_max = 0
self.y_ticks = []
self.max_height = application.screen_height
self.tuningPenTx = wx.Pen('Red', 1)
self.magnPen = wx.Pen('Black', 1)
self.phasePen = wx.Pen((0, 180, 0), 1)
self.swrPen = wx.Pen('Blue', 1)
self.backgroundPen = wx.Pen(self.GetBackgroundColour(), 1)
self.horizPen = wx.Pen(conf.color_gl, 1, wx.SOLID)
self.font = wx.Font(24, wx.FONTFAMILY_SWISS, wx.NORMAL,
wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
if sys.platform == 'win32':
self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
def OnEnter(self, event):
self.SetFocus() # Set focus so we get mouse wheel events
def OnPaint(self, event):
dc = wx.PaintDC(self)
x = self.tune_tx
dc.SetPen(self.tuningPenTx)
dc.DrawLine(x, 0, x, self.max_height)
dc.SetPen(self.horizPen)
for y in self.y_ticks:
dc.DrawLine(0, y, self.graph_width, y)
# Magnitude
t = 'Magnitude, '
x = self.chary
y = self.height - self.chary
dc.SetTextForeground(self.magnPen.GetColour())
dc.DrawText(t, x, y)
w, h = dc.GetTextExtent(t)
x += w + self.chary
# Phase
t = 'Phase, '
dc.SetTextForeground(self.phasePen.GetColour())
dc.DrawText(t, x, y)
w, h = dc.GetTextExtent(t)
x += w + self.chary
# SWR
t = 'SWR'
dc.SetTextForeground(self.swrPen.GetColour())
dc.DrawText(t, x, y)
w, h = dc.GetTextExtent(t)
x += w + self.chary
# Draw graph
if self.line_phase: # Phase line
# Try to avoid drawing vertical lines when the phase goes from +180 to -180
dc.SetPen(self.phasePen)
top = self.y_ticks[0]
high = self.y_ticks[1]
low = self.y_ticks[-2]
bottom = self.y_ticks[-1]
old_phase = self.line_phase[0]
line = [(0, old_phase)]
for x in range(1, self.graph_width):
phase = self.line_phase[x]
if phase < high and old_phase > low:
line.append((x-1, bottom))
dc.DrawLines(line)
line = [(x, top), (x, phase)]
elif phase > low and old_phase < high:
line.append((x-1, top))
dc.DrawLines(line)
line = [(x, bottom), (x, phase)]
else:
line.append((x, phase))
old_phase = phase
dc.DrawLines(line)
if self.line_mag: # Magnitude line
dc.SetPen(self.magnPen)
dc.DrawLines(self.line_mag)
if self.line_swr: # SWR line
dc.SetPen(self.swrPen)
dc.DrawLines(self.line_swr)
if self.display_text:
dc.SetFont(self.font)
dc.SetTextBackground(conf.color_graph)
dc.SetTextForeground('red')
dc.SetBackgroundMode(wx.SOLID)
dc.DrawText(self.display_text, 10, 50)
def SetHeight(self, height):
self.height = height
self.SetSize((self.graph_width, height))
def SetTuningLine(self, tune_tx):
dc = wx.ClientDC(self)
dc.SetPen(self.backgroundPen)
dc.DrawLine(self.tune_tx, 0, self.tune_tx, self.max_height)
dc.SetPen(self.tuningPenTx)
dc.DrawLine(tune_tx, 0, tune_tx, self.max_height)
self.tune_tx = tune_tx
self.Refresh()
class GraphScreen(wx.Window):
"""Display the graph screen X and Y axis, and create a graph display."""
def __init__(self, parent, data_width, graph_width, correct_width, correct_delta, in_splitter=0):
wx.Window.__init__(self, parent, pos = (0, 0))
self.in_splitter = in_splitter # Are we in the top of a splitter window?
self.data_width = data_width
self.graph_width = graph_width
self.correct_width = correct_width
self.correct_delta = correct_delta
self.started = False
self.doResize = False
self.pen_tick = wx.Pen("Black", 1, wx.SOLID)
self.font = wx.Font(10, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
self.SetFont(self.font)
w = self.GetCharWidth() * 14 // 10
h = self.GetCharHeight()
self.freq_start = 1000000
self.freq_stop = 2000000
self.charx = w
self.chary = h
self.mode = ''
self.data_mag = []
self.data_phase = []
self.data_impedance = []
self.data_reflect = []
self.data_freq = [0] * data_width
self.tick = max(2, h * 3 // 10)
self.originX = w * 5
self.offsetY = h + self.tick
self.width = self.originX * 2 + self.graph_width + self.tick
self.height = application.screen_height * 3 // 10
self.x0 = self.originX + self.graph_width // 2 # center of graph
self.originY = 10
self.num_ticks = 8 # number of Y lines above the X axis
self.dy_ticks = 10
# The pixel = slope * value + zero_pixel
# The value = (pixel - zero_pixel) / slope
self.leftZero = 10 # y location of left zero value
self.rightZero = 10 # y location of right zero value
self.leftSlope = 10 # slope of left scale times 360
self.rightSlope = 10 # slope of right scale times 360
self.SetSize((self.width, self.height))
self.SetSizeHints(self.width, 1, self.width)
self.SetBackgroundColour(conf.color_graph)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMotion)
self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
self.display = GraphDisplay(self, self.originX, 0, self.graph_width, 5, self.chary)
def OnPaint(self, event):
dc = wx.PaintDC(self)
if self.started and not self.in_splitter:
dc.SetFont(self.font)
self.MakeYTicks(dc)
self.MakeXTicks(dc)
def OnIdle(self, event):
if self.doResize:
self.ResizeGraph()
def OnSize(self, event):
self.doResize = True
self.ClearGraph()
event.Skip()
def ResizeGraph(self):
"""Change the height of the graph.
Changing the width interactively is not allowed.
Call after changing the zero or scale to recalculate the X and Y axis marks.
"""
w, h = self.GetClientSize()
if self.in_splitter: # Splitter window has no X axis scale
self.height = h
self.originY = h
else:
self.height = h - self.chary # Leave space for X scale
self.originY = self.height - self.offsetY
self.MakeYScale()
self.display.SetHeight(self.originY)
self.doResize = False
self.started = True
self.Refresh()
def MakeYScale(self):
chary = self.chary
dy = self.dy_ticks = (self.originY - chary * 2) // self.num_ticks # pixels per tick
ytot = dy * self.num_ticks
# Voltage dB scale
dbs = 80 # Number of dB to display
self.leftZero = self.originY - ytot - chary
self.leftSlope = - ytot * 360 // dbs # pixels per dB times 360
# Phase scale
self.rightSlope = - ytot # pixels per degree times 360
self.rightZero = self.originY - ytot // 2 - chary
# SWR scale
swrs = 9 # display range 1.0 to swrs
self.swrSlope = - ytot * 360 // (swrs - 1) # pixels per SWR unit times 360
self.swrZero = self.originY - self.swrSlope // 360 - chary
def MakeYTicks(self, dc):
charx = self.charx
chary = self.chary
x1 = self.originX - self.tick * 3 # left of tick mark
x2 = self.originX - 1 # x location of left y axis
x3 = self.originX + self.graph_width # end of graph data
x4 = x3 + 1 # right y axis
x5 = x3 + self.tick * 3 # right tick mark
dc.SetPen(self.pen_tick)
dc.DrawLine(x2, 0, x2, self.originY + 1) # y axis
dc.DrawLine(x4, 0, x4, self.originY + 1) # y axis
del self.display.y_ticks[:]
y = self.leftZero
dc.SetTextForeground(self.display.magnPen.GetColour())
for i in range(self.num_ticks + 1):
# Create the dB scale
val = (y - self.leftZero) * 360 // self.leftSlope
t = str(val)
dc.DrawLine(x1, y, x2, y)
self.display.y_ticks.append(y)
w, h = dc.GetTextExtent(t)
dc.DrawText(t, x1 - w, y - h // 2)
y += self.dy_ticks
y = self.leftZero
dc.SetTextForeground(self.display.phasePen.GetColour())
for i in range(self.num_ticks + 1):
# Create the scale on the right
val = (y - self.rightZero) * 360 // self.rightSlope
t = str(val)
dc.DrawLine(x4, y, x5, y)
w, h = dc.GetTextExtent(t)
dc.DrawText(t, self.width - w - charx, y - h // 2 + 3) # right text
y += self.dy_ticks
# Create the SWR scale
if self.mode == 'Reflection':
y = self.leftZero
dc.SetTextForeground(self.display.swrPen.GetColour())
for i in range(self.num_ticks + 1):
val = (y - self.swrZero) * 360 // self.swrSlope
t = str(val)
w, h = dc.GetTextExtent(t)
dc.DrawText(t, w//2, y - h // 2)
y += self.dy_ticks
def MakeXTicks(self, dc):
originY = self.originY
x3 = self.originX + self.graph_width # end of fft data
charx , z = dc.GetTextExtent('-30000XX')
tick0 = self.tick
tick1 = tick0 * 2
tick2 = tick0 * 3
dc.SetTextForeground(self.display.magnPen.GetColour())
# Draw the X axis
dc.SetPen(self.pen_tick)
dc.DrawLine(self.originX, originY, x3, originY)
sample_rate = int(self.freq_stop - self.freq_start)
if sample_rate < 12000:
return
VFO = int((self.freq_start + self.freq_stop) / 2)
# Draw the band plan colors below the X axis
x = self.originX
f = float(x - self.x0) * sample_rate / self.data_width
c = None
y = originY + 1
for freq, color in conf.BandPlan:
freq -= VFO
if f < freq:
xend = int(self.x0 + float(freq) * self.data_width / sample_rate + 0.5)
if c is not None:
dc.SetPen(wx.TRANSPARENT_PEN)
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(x, y, min(x3, xend) - x, tick0) # x axis
if xend >= x3:
break
x = xend
f = freq
c = color
stick = 1000 # small tick in Hertz
mtick = 5000 # medium tick
ltick = 10000 # large tick
# check the width of the frequency label versus frequency span
df = float(charx) * sample_rate / self.data_width # max label freq in Hertz
df *= 2.0
df = math.log10(df)
expn = int(df)
mant = df - expn
if mant < 0.3: # label every 10
tfreq = 10 ** expn
ltick = tfreq
mtick = ltick // 2
stick = ltick // 10
elif mant < 0.69: # label every 20
tfreq = 2 * 10 ** expn
ltick = tfreq // 2
mtick = ltick // 2
stick = ltick // 10
else: # label every 50
tfreq = 5 * 10 ** expn
ltick = tfreq
mtick = ltick // 5
stick = ltick // 10
# Draw the X axis ticks and frequency in kHz
dc.SetPen(self.pen_tick)
freq1 = VFO - sample_rate // 2
freq1 = (freq1 // stick) * stick
freq2 = freq1 + sample_rate + stick + 1
y_end = 0
for f in range (freq1, freq2, stick):
x = self.x0 + int(float(f - VFO) / sample_rate * self.data_width)
if self.originX <= x <= x3:
if f % ltick is 0: # large tick
dc.DrawLine(x, originY, x, originY + tick2)
elif f % mtick is 0: # medium tick
dc.DrawLine(x, originY, x, originY + tick1)
else: # small tick
dc.DrawLine(x, originY, x, originY + tick0)
if f % tfreq is 0: # place frequency label
t = str(f//1000)
w, h = dc.GetTextExtent(t)
dc.DrawText(t, x - w // 2, originY + tick2)
y_end = originY + tick2 + h
if y_end: # mark the center of the display
dc.DrawLine(self.x0, y_end, self.x0, application.screen_height)
def ClearGraph(self):
del self.display.line_mag[:]
del self.display.line_phase[:]
del self.display.line_swr[:]
del self.data_mag[:]
del self.data_phase[:]
del self.data_impedance[:]
del self.data_reflect[:]
self.display.Refresh()
def SetDisplayMsg(self, text=''):
self.display.display_text = text
self.display.Refresh()
def SetMode(self, mode):
self.mode = mode
def OnGraphData(self, volts):
# SWR = (1 + rho) / (1 - rho)
# Create graph lines
mode = self.mode
del self.display.line_mag[:]
del self.display.line_phase[:]
del self.display.line_swr[:]
del self.data_mag[:]
del self.data_phase[:]
del self.data_impedance[:]
del self.data_reflect[:]
if mode == 'Calibrate':
for x in range(application.correct_width):
self.calibrate_tmp[x] += volts[x]
self.calibrate_count += 1
for x in range(self.graph_width):
self.data_impedance.append(50)
self.data_reflect.append(0)
i = x * self.correct_width // self.data_width
magn = abs(volts[i])
phase = cmath.phase(volts[i]) * 360. / (2.0 * math.pi)
if magn < 1e-6:
db = -120.0
else:
db = 20.0 * math.log10(magn)
self.data_mag.append(db)
y = self.leftZero - int( - db * self.leftSlope / 360.0 + 0.5)
self.display.line_mag.append((x, y))
self.data_phase.append(phase)
y = self.rightZero - int( - phase * self.rightSlope / 360.0 + 0.5)
y = int(y)
self.display.line_phase.append(y)
elif mode == 'Reflection':
for x in range(self.graph_width):
delta = self.correct_delta
# Find the frequency for this pixel
freq = self.data_freq[x]
# Find the corresponding index into the correction array
i = int(freq / delta)
if i > self.correct_width - 2:
i = self.correct_width - 2
dd = float(freq - i * delta) / delta # fractional part of next index for linear interpolation
Vx = volts[x]
# linear interpolation
if application.reflection_short is not None and application.reflection_open is not None and application.reflection_load is not None:
Vs = application.reflection_short[i] + (application.reflection_short[i+1] - application.reflection_short[i]) * dd
Vo = application.reflection_open[i] + (application.reflection_open[i+1] - application.reflection_open[i]) * dd
Vl = application.reflection_load[i] + (application.reflection_load[i+1] - application.reflection_load[i]) * dd
S11 = Vl
VVop = Vo - S11
VVsh = Vs - S11
try:
S12S21 = 2.0 * VVop * VVsh / (VVsh - VVop)
S22 = (VVop + VVsh) / (VVop - VVsh)
reflect = (Vx - S11) / (S12S21 + S22 * (Vx - S11))
Z = 50.0 * (1.0 + reflect) / (1.0 - reflect)
except:
Z = 50E3
reflect = (Z - 50) / (Z + 50)
#print ('Vs Vo Vl', abs(Vs), abs(Vo), abs(Vl), 'S22', abs(S22), 'S1221', abs(S12S21))
else:
if application.reflection_open is not None:
correct = application.reflection_open[i] + (application.reflection_open[i+1] - application.reflection_open[i]) * dd
if application.reflection_short is not None:
correct = (correct - (application.reflection_short[i] + (application.reflection_short[i+1] - application.reflection_short[i]) * dd)) / 2.0
else: # Use Short
correct = - (application.reflection_short[i] + (application.reflection_short[i+1] - application.reflection_short[i]) * dd)
try:
reflect = volts[x] / correct
Z = 50.0 * (1.0 + reflect) / (1.0 - reflect)
except:
Z = 50E3
reflect = (Z - 50) / (Z + 50)
self.data_reflect.append(reflect)
self.data_impedance.append(Z)
magn = abs(reflect)
swr = (1.0 + magn) / (1.0 - magn)
if not 0.999 <= swr <= 99:
swr = 99.0
if magn < 1e-6:
db = -120.0
else:
db = 20.0 * math.log10(magn)
self.data_mag.append(db)
y = self.leftZero - int( - db * self.leftSlope / 360.0 + 0.5)
self.display.line_mag.append((x, y))
phase = cmath.phase(reflect) * 360. / (2.0 * math.pi)
self.data_phase.append(phase)
y = self.rightZero - int( - phase * self.rightSlope / 360.0 + 0.5)
y = int(y)
self.display.line_phase.append(y)
y = self.swrZero - int( - swr * self.swrSlope / 360.0 + 0.5)
self.display.line_swr.append((x,y))
else: # Mode is transmission
for x in range(self.graph_width):
delta = self.correct_delta
# Find the frequency for this pixel
freq = self.data_freq[x]
# Find the corresponding index into the correction array
i = int(freq / delta)
if i > self.correct_width - 2:
i = self.correct_width - 2
dd = float(freq - i * delta) / delta # fractional part of next index for linear interpolation
trans = volts[x]
if application.transmission_open is not None:
trans -= application.transmission_open[i] + (application.transmission_open[i+1] - application.transmission_open[i]) * dd
trans /= application.transmission_short[i] + (application.transmission_short[i+1] - application.transmission_short[i]) * dd
self.data_reflect.append(trans)
self.data_impedance.append(50)
magn = abs(trans)
if magn < 1e-6:
db = -120.0
else:
db = 20.0 * math.log10(magn)
self.data_mag.append(db)
y = self.leftZero - int( - db * self.leftSlope / 360.0 + 0.5)
self.display.line_mag.append((x, y))
phase = cmath.phase(trans) * 360. / (2.0 * math.pi)
self.data_phase.append(phase)
y = self.rightZero - int( - phase * self.rightSlope / 360.0 + 0.5)
y = int(y)
self.display.line_phase.append(y)
self.display.Refresh()
def NewFreq(self, start, stop):
if self.freq_start != start or self.freq_stop != stop:
self.ClearGraph()
self.freq_start = start
self.freq_stop = stop
for i in range(self.data_width): # The frequency in Hertz for every graph pixel
self.data_freq[i] = int(start + float(stop - start) * i / (self.data_width - 1) + 0.5)
self.SetTxFreq(index=self.display.tune_tx)
self.doResize = True
def SetTxFreq(self, freq=None, index=None):
if index is None:
index = int(float(freq - self.freq_start) * (self.data_width - 1) / (self.freq_stop - self.freq_start) + 0.5)
if index < 0:
index = 0
elif index >= self.data_width:
index = self.data_width - 1
if freq is None:
freq = self.data_freq[index]
self.display.SetTuningLine(index)
application.ShowFreq(freq, index)
def GetMousePosition(self, event):
"""For mouse clicks in our display, translate to our screen coordinates."""
mouse_x, mouse_y = event.GetPosition()
win = event.GetEventObject()
if win is not self:
x, y = win.GetPosition().Get()
mouse_x += x
mouse_y += y
return mouse_x, mouse_y
def OnLeftDown(self, event):
mouse_x, mouse_y = self.GetMousePosition(event)
self.SetTxFreq(index=mouse_x - self.originX)
self.CaptureMouse()
def OnLeftUp(self, event):
if self.HasCapture():
self.ReleaseMouse()
def OnMotion(self, event):
if event.Dragging() and event.LeftIsDown():
mouse_x, mouse_y = self.GetMousePosition(event)
self.SetTxFreq(index=mouse_x - self.originX)
def OnWheel(self, event):
tune = self.display.tune_tx + event.GetWheelRotation() // event.GetWheelDelta()
self.SetTxFreq(index=tune)
class HelpScreen(wx.html.HtmlWindow):
"""Create the screen for the Help button."""
def __init__(self, parent, width, height):
wx.html.HtmlWindow.__init__(self, parent, -1, size=(width, height))
if "gtk2" in wx.PlatformInfo:
self.SetStandardFonts()
self.SetFonts("", "", [10, 12, 14, 16, 18, 20, 22])
# read in text from file help.html in the directory of this module
self.LoadFile('help_vna.html')
def OnLinkClicked(self, link):
webbrowser.open(link.GetHref(), new=2)
class QMainFrame(wx.Frame):
"""Create the main top-level window."""
def __init__(self, width, height):
fp = open('__init__.py') # Read in the title
self.title = fp.readline().strip()
fp.close()
self.title = 'Quisk Vector Network Analyzer ' + self.title[7:]
wx.Frame.__init__(self, None, -1, self.title, wx.DefaultPosition,
(width, height), wx.DEFAULT_FRAME_STYLE, 'MainFrame')
self.SetBackgroundColour(conf.color_bg)
self.Bind(wx.EVT_CLOSE, self.OnBtnClose)
def OnBtnClose(self, event):
application.OnBtnClose(event)
self.Destroy()
def SetConfigText(self, text):
if len(text) > 100:
text = text[0:80] + '|||' + text[-17:]
self.SetTitle("Radio %s %s %s" % (configure.Settings[1], self.title, text))
class Spacer(wx.Window):
"""Create a bar between the graph screen and the controls"""
def __init__(self, parent):
wx.Window.__init__(self, parent, pos = (0, 0),
size=(-1, 6), style = wx.NO_BORDER)
self.Bind(wx.EVT_PAINT, self.OnPaint)
r, g, b = parent.GetBackgroundColour().Get(False)
dark = (r * 7 // 10, g * 7 // 10, b * 7 // 10)
light = (r + (255 - r) * 5 // 10, g + (255 - g) * 5 // 10, b + (255 - b) * 5 // 10)
self.dark_pen = wx.Pen(dark, 1, wx.SOLID)
self.light_pen = wx.Pen(light, 1, wx.SOLID)
self.width = application.screen_width
def OnPaint(self, event):
dc = wx.PaintDC(self)
w = self.width
dc.SetPen(self.dark_pen)
dc.DrawLine(0, 0, w, 0)
dc.DrawLine(0, 1, w, 1)
dc.DrawLine(0, 2, w, 2)
dc.SetPen(self.light_pen)
dc.DrawLine(0, 3, w, 3)
dc.DrawLine(0, 4, w, 4)
dc.DrawLine(0, 5, w, 5)
class CalibrateDialog(wx.Dialog):
def __init__(self, app):
self.app = app
self.correct_open = None
self.correct_short = None
self.correct_load = None
w, h = app.main_frame.GetSize().Get()
width = w // 2
if app.screen_name == "Reflection":
title = "Calibrate for Reflection Mode"
t = ''
if app.reflection_short is not None:
t += "Short"
if app.reflection_open is not None:
t += "Open"
if app.reflection_load is not None:
t += "Load"
if t:
t = "Reflection mode calibration is %s from %s" % (t, app.calibrate_time)
else:
t = "Reflection mode is Uncalibrated"
else:
title = "Calibrate for Transmission Mode"
t = ''
if app.transmission_short is not None:
t += "Short"
if app.transmission_open is not None:
t += "Open"
if t:
t = "Transmission mode calibration is %s from %s" % (t, app.calibrate_time)
else:
t = "Transmission mode is Uncalibrated"
wx.Dialog.__init__(self, None, -1, title, size=(width, h))
tab = self.GetCharHeight() * 2
y = tab
txt = wx.StaticText(self, -1, t, pos=(tab, y))
z, chary = txt.GetSize().Get()
y += chary * 3 // 2
if app.screen_name == "Reflection":
t = "To calibrate the VNA for reflection mode, connect the standard Short, Open and Load connectors to the unknown port, and press the button."
t += " Reflection mode requires at least an Open or Short calibration, but using all three is highly recommended."
else:
t = "To calibrate the VNA for transmission mode, connect the cables together for Short, or leave them unconnected for Open, and press the button."
t += " The Short calibration is required, but the Open calibration is optional."
t += " The calibration will be saved for use the next time the program starts."
txt = wx.StaticText(self, -1, t, pos=(tab, y))
txt.Wrap(width - tab * 2)
w, h = txt.GetSize().Get()
y += h + chary
# Calibrate buttons
t1 = wx.StaticText(self, -1, "Connect the Short connector and press", pos=(tab, y))
tw, th = t1.GetSize().Get()
bx = tab + tw + tab // 2
b1 = QuiskPushbutton(self, self.OnBtnShort, " Short ")
b1.SetColorGray()
bw, bh = b1.GetSize().Get()
by = y + (th - bh) // 2
b1.Move(wx.Point(bx, by))
self.txt_short = wx.StaticText(self, -1, "Not done", pos=(bx + bw + tab // 2, y))
y = by + bh * 15 // 10
by = y + (th - bh) // 2
t2 = wx.StaticText(self, -1, "Connect the Open connector and press", pos=(tab, y), size = (tw, th))
b2 = QuiskPushbutton(self, self.OnBtnOpen, "Open")
b2.SetColorGray()
b2.SetPosition((bx, by))
b2.SetSize((bw, bh))
self.txt_open = wx.StaticText(self, -1, "Not done", pos=(bx + bw + tab // 2, y))
y = by + bh * 15 // 10
by = y + (th - bh) // 2
if app.screen_name == "Reflection":
t3 = wx.StaticText(self, -1, "Connect the Load connector and press", pos=(tab, y), size = (tw, th))
b3 = QuiskPushbutton(self, self.OnBtnLoad, "Load")
b3.SetColorGray()
b3.SetPosition((bx, by))
b3.SetSize((bw, bh))
self.txt_load = wx.StaticText(self, -1, "Not done", pos=(bx + bw + tab // 2, y))
y = by + bh * 15 // 10
# Calibrate buttons
b1 = QuiskPushbutton(self, self.OnBtnCalibrate, " Calibrate ")
b1.SetColorGray()
b1.Enable(False)
w, h = b1.GetSize().Get()
b2 = QuiskPushbutton(self, self.OnBtnCancel, "Cancel")
b2.SetColorGray()
b2.SetSize((w, h))
ww = (width - w * 2 - 40) // 3
b1.Move(wx.Point(ww, y))
b2.Move(wx.Point(width - w - ww, y))
y += h * 3 // 2
self.SetClientSize(wx.Size(width, y))
self.btns = [b1, b2]
# timer for calibrate buttons
self.calibrate_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnCalibrateTimer, self.calibrate_timer)
def OnBtnCalibrate(self, event):
app = self.app
if app.screen_name == "Reflection":
app.reflection_short = self.correct_short
app.reflection_open = self.correct_open
app.reflection_load = self.correct_load
elif app.screen_name == "Transmission":
app.transmission_short = self.correct_short
app.transmission_open = self.correct_open
app.calibrate_time = time.asctime()
app.EnableButtons()
app.SetCalText()
app.SaveState()
self.EndModal(4)
def OnBtnCancel(self, event):
self.EndModal(5)
def Calibrate(self):
for b in self.btns:
b.Enable(False)
self.app.Calibrate()
self.calibrate_timer.Start(3000, oneShot=True)
def OnBtnShort(self, event):
self.txt_short.SetLabel("Wait")
self.mode = "Short"
self.Calibrate()
def OnBtnOpen(self, event):
self.txt_open.SetLabel("Wait")
self.mode = "Open"
self.Calibrate()
def OnBtnLoad(self, event):
self.txt_load.SetLabel("Wait")
self.mode = "Load"
self.Calibrate()
def OnCalibrateTimer(self, event):
self.app.running = False
if self.app.has_SetVNA:
Hardware.SetVNA(key_down=0)
for b in self.btns:
b.Enable(True)
data = self.app.graph.calibrate_tmp
count = self.app.graph.calibrate_count
if count == 0:
if self.mode == "Short":
self.txt_short.SetLabel("Not done")
elif self.mode == "Open":
self.txt_open.SetLabel("Not done")
elif self.mode == "Load":
self.txt_load.SetLabel("Not done")
return
for i in range(application.correct_width):
data[i] /= count
if self.mode == "Short":
self.txt_short.SetLabel("Done")
self.correct_short = data
elif self.mode == "Open":
self.txt_open.SetLabel("Done")
self.correct_open = data
elif self.mode == "Load":
self.txt_load.SetLabel("Done")
self.correct_load = data
class App(wx.App):
"""Class representing the application."""
StateNames = ['transmission_open', 'transmission_short', 'reflection_open', 'reflection_short', 'reflection_load', 'calibrate_time',
'calibrate_version']
def __init__(self):
global application
application = self
self.bottom_widgets = None
self.is_vna_program = None
if sys.stdout.isatty():
wx.App.__init__(self, redirect=False)
else:
wx.App.__init__(self, redirect=True)
def OnInit(self):
"""Perform most initialization of the app here (called by wxPython on startup)."""
wx.lib.colourdb.updateColourDB() # Add additional color names
import quisk_widgets # quisk_widgets needs the application object
quisk_widgets.application = self
del quisk_widgets
global conf # conf is the module for all configuration data
import quisk_conf_defaults as conf
setattr(conf, 'config_file_path', ConfigPath)
setattr(conf, 'DefaultConfigDir', DefaultConfigDir)
if os.path.isfile(ConfigPath): # See if the user has a config file
setattr(conf, 'config_file_exists', True)
d = {}
d.update(conf.__dict__) # make items from conf available
exec(compile(open(ConfigPath).read(), ConfigPath, 'exec'), d) # execute the user's config file
if os.path.isfile(ConfigPath2): # See if the user has a second config file
exec(compile(open(ConfigPath2).read(), ConfigPath2, 'exec'), d) # execute the user's second config file
for k in d: # add user's config items to conf
v = d[k]
if k[0] != '_': # omit items starting with '_'
setattr(conf, k, v)
else:
setattr(conf, 'config_file_exists', False)
# Read in configuration from the selected radio
if configure: self.local_conf = configure.Configuration(self, argv_options.AskMe)
if configure: self.local_conf.UpdateConf()
# Choose whether to use Unicode or text symbols
for k in ('sym_stat_mem', 'sym_stat_fav', 'sym_stat_dx',
'btn_text_range_dn', 'btn_text_range_up', 'btn_text_play', 'btn_text_rec', 'btn_text_file_rec',
'btn_text_file_play', 'btn_text_fav_add',
'btn_text_fav_recall', 'btn_text_mem_add', 'btn_text_mem_next', 'btn_text_mem_del'):
if conf.use_unicode_symbols:
setattr(conf, 'X' + k, getattr(conf, 'U' + k))
else:
setattr(conf, 'X' + k, getattr(conf, 'T' + k))
MakeWidgetGlobals()
self.BtnRfGain = None
self.graph_freq = 7e6
self.graph_index = 50
self.transmission_open = None
self.transmission_short = None
self.reflection_open = None
self.reflection_short = None
self.reflection_load = None
self.reflection_cal = "Cal x"
self.transmission_cal = "Cal x"
self.calibrate_time = time.asctime()
self.calibrate_version = 1
QS.set_params(quisk_is_vna=1) # Call this only if we are the VNA program
# Open hardware file
self.firmware_version = None
global Hardware
if configure and self.local_conf.GetHardware():
pass
else:
if hasattr(conf, "Hardware"): # Hardware defined in config file
self.Hardware = conf.Hardware(self, conf)
hname = ConfigPath
else:
self.Hardware = conf.quisk_hardware.Hardware(self, conf)
hname = conf.quisk_hardware.__file__
if hname[-3:] == 'pyc':
hname = hname[0:-1]
setattr(conf, 'hardware_file_name', hname)
if conf.quisk_widgets:
hname = conf.quisk_widgets.__file__
if hname[-3:] == 'pyc':
hname = hname[0:-1]
setattr(conf, 'widgets_file_name', hname)
else:
setattr(conf, 'widgets_file_name', '')
Hardware = self.Hardware
# Initialization
if configure: self.local_conf.Initialize()
# get the screen size
x, y, self.screen_width, self.screen_height = wx.Display().GetGeometry()
self.Bind(wx.EVT_QUERY_END_SESSION, self.OnEndSession)
self.sample_rate = 48000
self.timer = time.time() # A seconds clock
self.time0 = 0 # timer to display fields
self.clip_time0 = 0 # timer to display a CLIP message on ADC overflow
self.heart_time0 = self.timer # timer to call HeartBeat at intervals
self.running = False
self.startup = True
self.save_data = []
self.frequency = 0
self.main_frame = frame = QMainFrame(10, 10)
self.SetTopWindow(frame)
# Find the data width, the width of returned graph data.
width = self.screen_width * conf.graph_width
width = int(width)
self.data_width = width
# correct_delta is the spacing of correction points in Hertz
if conf.use_rx_udp == 10: # Hermes UDP protocol
self.max_freq = 30000000 # maximum calculation frequency
self.correct_width = self.data_width # number of data points in the correct arrays
else:
self.max_freq = 60000000
self.correct_width = self.max_freq // 15000 + 4
if hasattr(Hardware, 'SetVNA'):
self.has_SetVNA = True
start, stop = Hardware.SetVNA(vna_start=0, vna_stop=self.max_freq, vna_count=self.correct_width)
self.correct_delta = float(stop - start) / (self.correct_width - 1)
Hardware.SetVNA(vna_count=self.data_width)
else:
self.has_SetVNA = False
self.correct_delta = 1
# Restore persistent program state
self.init_path = os.path.join(os.path.dirname(ConfigPath), '.quisk_vna_init.pkl')
try:
fp = open(self.init_path, "r")
d = pickle.load(fp)
fp.close()
for k in d:
v = d[k]
if k in self.StateNames:
setattr(self, k, v)
except:
pass #traceback.print_exc()
# Record the basic application parameters
if sys.platform == 'win32':
h = self.main_frame.GetHandle()
else:
h = 0
QS.set_enable_bandscope(0)
# FFT size must equal the data_width so that all data points are returned!
QS.record_app(self, conf, self.data_width, self.data_width, self.data_width,
1, self.sample_rate, h)
# Make all the screens and hide all but one
self.graph = GraphScreen(frame, self.data_width, self.data_width, self.correct_width, self.correct_delta)
self.screen = self.graph
width = self.graph.width
self.help_screen = HelpScreen(frame, width, self.screen_height // 10)
self.help_screen.Hide()
# Make a vertical box to hold all the screens and the bottom rows
vertBox = self.vertBox = wx.BoxSizer(wx.VERTICAL)
frame.SetSizer(vertBox)
# Add the screens
vertBox.Add(self.graph, 1)
vertBox.Add(self.help_screen, 1)
# Add the spacer
vertBox.Add(Spacer(frame), 0, wx.EXPAND)
# Add the sizer for the buttons
szr1 = wx.BoxSizer(wx.HORIZONTAL)
vertBox.Add(szr1, 0, wx.EXPAND, 0)
# Make the buttons in row 1
self.buttons1 = buttons1 = []
self.screen_name = "Reflection"
self.graph.SetMode(self.screen_name)
b = RadioButtonGroup(frame, self.OnBtnScreen, (' Transmission ', 'Reflection', 'Help'), self.screen_name)
buttons1 += b.buttons
self.btn_run = b = QuiskCheckbutton(frame, self.OnBtnRun, 'Run')
buttons1.append(b)
self.btn_calibrate = b = QuiskPushbutton(frame, self.OnBtnCal, 'Calibrate..')
buttons1.append(b)
width = 0
for b in buttons1:
w, height = b.GetMinSize()
if width < w:
width = w
for i in range(24, 8, -2):
font = wx.Font(i, wx.FONTFAMILY_SWISS, wx.NORMAL, wx.FONTWEIGHT_NORMAL, False, conf.quisk_typeface)
frame.SetFont(font)
w, h = frame.GetTextExtent('Start ')
if h < height * 9 // 10:
break
for b in buttons1:
b.SetMinSize((width, height))
# Frequency entry start and stop
t = wx.lib.stattext.GenStaticText(frame, -1, 'Start ')
t.SetFont(font)
t.SetBackgroundColour(conf.color_bg)
gap = max(2, height//8)
freq0 = t
e = wx.TextCtrl(frame, -1, '1', style=wx.TE_PROCESS_ENTER)
e.SetFont(font)
tw, z = e.GetTextExtent("xx30.333xxxxx")
e.SetMinSize((tw, height))
e.SetBackgroundColour(conf.color_entry)
self.freq_start_ctrl = e
frame.Bind(wx.EVT_TEXT_ENTER, self.OnNewFreq, source=e)
frame.Bind(wx.EVT_TEXT, self.OnNewFreq, source=e)
t = wx.lib.stattext.GenStaticText(frame, -1, 'Stop ')
t.SetFont(font)
t.SetBackgroundColour(conf.color_bg)
freq2 = t
e = wx.TextCtrl(frame, -1, '30', style=wx.TE_PROCESS_ENTER)
e.SetFont(font)
e.SetMinSize((tw, height))
e.SetBackgroundColour(conf.color_entry)
self.freq_stop_ctrl = e
frame.Bind(wx.EVT_TEXT_ENTER, self.OnNewFreq, source=e)
frame.Bind(wx.EVT_TEXT, self.OnNewFreq, source=e)
# Band buttons
ilst = []
slst = []
for l in conf.BandEdge: # Sort keys
if not (l in conf.bandLabels or l == '60'):
continue
try:
ilst.append((int(l), conf.BandEdge[l]))
except ValueError: # item is a string, not an integer
slst.append((l, conf.BandEdge[l]))
ilst.sort()
ilst.reverse()
slst.sort()
band = []
width = 0
for l in ilst + slst:
b = QuiskPushbutton(frame, self.OnBtnBand, str(l[0]))
b.bandEdge = l[1]
band.append(b)
w, h= b.GetMinSize()
if width < w:
width = w
# make a list of all buttons
self.buttons = buttons1 + band
# Add button row to sizer
gap = max(2, height // 8)
gap2 = max(2, height // 4)
szr1.Add(buttons1[0], 0, wx.RIGHT|wx.LEFT, gap)
szr1.Add(buttons1[1], 0, wx.RIGHT, gap)
szr1.Add(buttons1[2], 0, wx.RIGHT, gap)
szr1.Add(buttons1[3], 0, wx.RIGHT|wx.LEFT, gap2)
szr1.Add(buttons1[4], 0, wx.RIGHT|wx.LEFT, gap)
szr1.Add(freq0, 0, wx.ALIGN_CENTER_VERTICAL)
szr1.Add(self.freq_start_ctrl, 0, wx.RIGHT, gap)
szr1.Add(freq2, 0, wx.ALIGN_CENTER_VERTICAL)
szr1.Add(self.freq_stop_ctrl, 0, wx.RIGHT, gap)
for x in band:
szr1.Add(x, 1, wx.RIGHT, gap)
self.statusbar = self.main_frame.CreateStatusBar()
# Set top window size
self.main_frame.SetClientSize(wx.Size(self.graph.width, self.screen_height * 5 // 10))
w, h = self.main_frame.GetSize().Get()
self.main_frame.SetSizeHints(w, 1, w)
if hasattr(Hardware, 'pre_open'): # pre_open() is called before open()
Hardware.pre_open()
if conf.use_rx_udp == 10: # Hermes UDP protocol
self.add_version = False
conf.tx_ip = Hardware.hermes_ip
conf.tx_audio_port = conf.rx_udp_port
elif conf.use_rx_udp:
self.add_version = True # Add firmware version to config text
conf.rx_udp_decimation = 8 * 8 * 8
if not conf.tx_ip:
conf.tx_ip = conf.rx_udp_ip
if not conf.tx_audio_port:
conf.tx_audio_port = conf.rx_udp_port + 2
else:
self.add_version = False
# Open the hardware. This must be called before open_sound().
self.config_text = Hardware.open()
self.status_error = "No hardware response" # possible error messages
if self.config_text:
self.main_frame.SetConfigText(self.config_text)
if conf.use_rx_udp == 10: # Hermes UDP protocol
if self.config_text[0:12] == "Capture from":
self.status_error = ''
else:
self.config_text = "Missing config_text"
# Note: Subsequent calls to set channels must not name a higher channel number.
# Normally, these calls are only used to reverse the channels.
QS.open_sound(conf.name_of_sound_capt, '', self.sample_rate,
conf.data_poll_usec, conf.latency_millisecs,
'', conf.tx_ip, conf.tx_audio_port,
48000, 0, 0, 1.0, '', 48000)
self.Bind(wx.EVT_IDLE, self.graph.OnIdle)
frame.Show()
self.NewFreq(1000000, 30000000)
self.SetCalText()
self.WriteFields()
self.EnableButtons()
QS.set_fdx(1)
QS.set_rx_mode(0)
self.sound_thread = SoundThread()
self.sound_thread.start()
return True
def OnExit(self):
QS.close_rx_udp()
##self.local_conf.SaveState() # to save default radio selection
return 0
def SaveState(self):
if self.init_path: # save current program state
d = {}
for n in self.StateNames:
d[n] = getattr(self, n)
try:
fp = open(self.init_path, "w")
pickle.dump(d, fp)
fp.close()
except:
pass #traceback.print_exc()
def OnEndSession(self, event):
event.Skip()
self.OnBtnClose(event)
def OnBtnClose(self, event):
if self.has_SetVNA:
Hardware.SetVNA(key_down=0, do_tx=True)
time.sleep(0.5)
if self.sound_thread:
self.sound_thread.stop()
for i in range(0, 20):
if threading.activeCount() == 1:
break
time.sleep(0.1)
Hardware.close()
def OnBtnBand(self, event):
btn = event.GetEventObject()
start, stop = btn.bandEdge
start = float(start) * 1e-6
stop = float(stop) * 1e-6
self.freq_start_ctrl.SetValue(str(start))
self.freq_stop_ctrl.SetValue(str(stop))
def Calibrate(self):
self.graph.calibrate_tmp = [0] * self.correct_width
self.graph.calibrate_count = 0
self.graph.SetMode("Calibrate")
self.NewFreq(0, self.max_freq)
if self.has_SetVNA:
Hardware.SetVNA(key_down=1)
self.running = True
self.startup = True
def OnBtnCal(self, event):
if self.has_SetVNA:
Hardware.SetVNA(key_down=0, vna_start=0, vna_stop=self.max_freq, vna_count=self.correct_width)
dlg = CalibrateDialog(self)
dlg.ShowModal()
dlg.Destroy()
if application.has_SetVNA:
Hardware.SetVNA(key_down=0, vna_count=self.data_width)
def OnBtnScreen(self, event):
btn = event.GetEventObject()
self.screen_name = btn.GetLabel().strip()
if self.screen_name == 'Help':
self.help_screen.Show()
self.graph.Hide()
else:
self.help_screen.Hide()
self.graph.Show()
self.graph.SetMode(self.screen_name)
self.vertBox.Layout()
self.EnableButtons()
def OnBtnRun(self, event):
btn = event.GetEventObject()
run = btn.GetValue()
if run:
for b in self.buttons1:
if b != btn:
b.Enable(False)
else:
for b in self.buttons1:
b.Enable(True)
self.graph.SetMode(self.screen_name)
if not self.running and not self.OnNewFreq():
return
if self.has_SetVNA:
if run:
self.running = True
self.startup = True
Hardware.SetVNA(key_down=1)
else:
self.running = False
Hardware.SetVNA(key_down=0)
def EnableButtons(self):
if self.screen_name == 'Transmission':
if self.transmission_short is not None and len(self.transmission_short) == self.correct_width:
self.btn_run.Enable(1)
else:
self.btn_run.Enable(0)
elif self.screen_name == 'Reflection':
if (self.reflection_short is not None or self.reflection_open is not None) and len(self.reflection_short) == self.correct_width:
self.btn_run.Enable(1)
else:
self.btn_run.Enable(0)
else: # Help
self.btn_run.Enable(0)
def ShowFreq(self, freq, index):
self.frequency = freq
if hasattr(Hardware, 'ChangeFilterFrequency'):
Hardware.ChangeFilterFrequency(freq)
self.graph_freq = freq
self.graph_index = index
self.WriteFields()
def OnNewFreq(self, event=None):
if self.status_error and self.status_error[0:15] != "Error in Start ":
return False
try:
start = self.freq_start_ctrl.GetValue()
start = float(start) * 1e6
stop = self.freq_stop_ctrl.GetValue()
stop = float(stop) * 1e6
except:
self.status_error = "Error in Start or Stop freq"
#traceback.print_exc()
return False
start = int(start + 0.5)
stop = int(stop + 0.5)
if start > stop:
self.status_error = "Error in Start or Stop freq"
return False
if stop > self.max_freq:
stop = self.max_freq
self.freq_stop_ctrl.SetValue("%.6f" % (stop * 1.E-6))
self.status_error = ''
self.NewFreq(start, stop)
return True
def NewFreq(self, start, stop):
if application.has_SetVNA:
start, stop = Hardware.SetVNA(vna_start=start, vna_stop=stop)
self.graph.NewFreq(start, stop)
def SetCalText(self):
text = ''
if self.reflection_short is not None:
text += "S"
if self.reflection_open is not None:
text += "O"
if self.reflection_load is not None:
text += "L"
if text:
text = "Cal " + text
else:
text = "Cal x"
self.reflection_cal = text
text = ''
if self.transmission_short is not None:
text += "S"
if self.transmission_open is not None:
text += "O"
if text:
text = "Cal " + text
else:
text = "Cal x"
self.transmission_cal = text
def WriteFields(self):
index = self.graph_index
if index < 0:
index = 0
elif index >= self.data_width:
index = self.data_width - 1
freq = "Freq %.6f" % (self.frequency * 1E-6)
mode = self.graph.mode
if self.status_error:
text = self.status_error
elif not self.graph.data_mag:
if mode == 'Transmission':
text = u" %s %s" % (self.transmission_cal, freq)
elif mode == 'Reflection':
text = u" %s %s" % (self.reflection_cal, freq)
else:
text = ''
elif mode == 'Calibrate':
db = self.graph.data_mag[index]
phase = self.graph.data_phase[index]
text = u" %s Calibrate %.2f dB %.1f\u00B0" % (freq, db, phase)
elif mode == 'Transmission':
db = self.graph.data_mag[index]
phase = self.graph.data_phase[index]
text = u" %s %s Transmission %.2f dB %.1f\u00B0" % (self.transmission_cal, freq, db, phase)
elif mode == 'Reflection':
db = self.graph.data_mag[index]
phase = self.graph.data_phase[index]
aref = abs(self.graph.data_reflect[index])
swr = (1.0 + aref) / (1.0 - aref)
if not 0.999 <= swr <= 99:
swr = 99.0
text = u" %s %s Reflect ( %.2f dB %.1f\u00B0 ) SWR %.1f" % (self.reflection_cal, freq, db, phase, swr)
Z = self.graph.data_impedance[index]
mag = abs(Z)
phase = cmath.phase(Z) * 360. / (2.0 * math.pi)
freq = self.graph.data_freq[index]
z_real = Z.real
z_imag = Z.imag
if z_imag < 0:
text += u" Z \u03A9 ( %.1f - %.1fJ ) = ( %.1f %.1f\u00B0 )" % (z_real, abs(z_imag), mag, phase)
else:
text += u" Z \u03A9 ( %.1f + %.1fJ ) = ( %.1f %.1f\u00B0 )" % (z_real, z_imag, mag, phase)
if z_imag >= 0.5:
L = z_imag / (2.0 * math.pi * freq) * 1e9
Xp = (z_imag ** 2 + z_real ** 2) / z_imag
Lp = Xp / (2.0 * math.pi * freq) * 1e9
text += ' L %.0f nH' % L
if z_real > 0.01:
Rp = (z_imag ** 2 + z_real ** 2) / z_real
text += " ( %.1f || %.0f nH )" % (Rp, Lp)
elif z_imag < -0.5:
C = -1.0 / (2.0 * math.pi * freq * z_imag) * 1e9
Xp = (z_imag ** 2 + z_real ** 2) / z_imag
Cp = -1.0 / (2.0 * math.pi * freq * Xp) * 1e9
text += ' C %.3f nF' % C
if z_real > 0.01:
Rp = (z_imag ** 2 + z_real ** 2) / z_real
text += " ( %.1f || %.3f nF )" % (Rp, Cp)
self.statusbar.SetStatusText(text)
def PostStartup(self): # called once after sound attempts to start
pass
def OnReadSound(self): # called at frequent intervals
self.timer = time.time()
dat = QS.get_graph(0, 1.0, 0)
if dat and self.running:
dat = list(dat)
try:
start = dat.index(0)
except ValueError:
self.save_data += dat
return
data = self.save_data + dat[0:start]
self.save_data = dat[start+1:]
if self.graph.mode == 'Calibrate':
if len(data) != self.correct_width:
if DEBUG: print(' bad calibrate array', len(data), self.correct_width)
return
else:
if len(data) != self.data_width:
if DEBUG: print(' bad data array', len(data), self.data_width)
return
for i in range(len(data)):
data[i] /= 2147483647.0
if self.startup: # always skip the first block of data
self.startup = False
else:
self.graph.OnGraphData(data)
if QS.get_overrange() and self.running:
self.clip_time0 = self.timer
self.status_error = " *** CLIP ***"
self.graph.SetDisplayMsg("Clip")
if self.clip_time0:
if self.timer - self.clip_time0 > 1.0:
self.clip_time0 = 0
self.status_error = ''
self.graph.SetDisplayMsg()
if self.timer - self.heart_time0 > 0.10: # call hardware to perform background tasks
self.heart_time0 = self.timer
Hardware.HeartBeat()
if self.add_version and self.firmware_version is None:
self.firmware_version = Hardware.GetFirmwareVersion()
if self.firmware_version is not None:
if self.firmware_version < 3:
self.status_error = "Need firmware ver 3"
else:
self.status_error = ''
# Set text fields
if self.timer - self.time0 > 0.5:
self.time0 = self.timer
#print "len %5d re %9.6f im %9.6f mag %9.6f phase %7.2f" % (len(data),
# volts.real, volts.imag, abs(volts), phase)
#print "Z re %12.2f im %12.2f mag %12.2f phase %7.2f" % (zzz.real, zzz.imag,
# abs(zzz), cmath.phase(zzz) * 360. / (2.0 * math.pi))
self.WriteFields()
def main():
"""If quisk is installed as a package, you can run it with quisk.main()."""
App()
application.MainLoop()
if __name__ == '__main__':
main()